Psyter - WindowsService Detailed Structure (Part 4 of 4)¶
Part 4 Overview¶
This final document covers:
- FCM Notification System: Firebase Cloud Messaging for push notifications and reminders
- Supporting Features: SCHFS expiry notifications and log file cleanup
- Data Transfer Objects (DTO): All 5 DTO model classes
- Deployment: Advanced Installer project and installation guide
FCM Notification System¶
The service implements Firebase Cloud Messaging (FCM) for push notifications to mobile apps (Android and iOS).
1. Get Pending FCM Notifications and Reminders (GetPendingFCMNotificationsAndReminders)¶
File: WindowsService/PsyterPaymentInquiry/PaymentInquiryService.cs
Method Overview¶
public void GetPendingFCMNotificationsAndReminders()
Purpose: Retrieve and send pending FCM notifications and appointment reminders to users
Execution Frequency: Every 10 minutes (timer interval: 600,000 ms)
Code Analysis¶
Step 1: Retrieve Pending Notifications and Reminders¶
try
{
PaymentDataAccess dal = new PaymentDataAccess();
var response = dal.GetPendingFCMNotificationsAndRemindersToSend();
WriteToFile("Pending Notifications Count : " + response.UserNotificationsList.Count, "SendFCMNotificationAndReminders");
What It Does:
1. Creates DAL instance
2. Calls GetPendingFCMNotificationsAndRemindersToSend() to retrieve:
- User notifications (general notifications)
- User reminders (appointment reminders)
- FCM configuration (API URL, authorization key)
3. Logs pending notification count
Data Retrieved:
- UserNotificationsList: List of system-generated notifications
- UserRemindersList: List of appointment reminders
- FCMConfiguration: Firebase Cloud Messaging credentials
Step 2: Process User Notifications (Commented Out)¶
if(response.UserNotificationsList.Count > 0)
{
//foreach(var notification in response.UserNotificationsList)
//{
// object template = new
// {
// NotificationType = 11, //common notification
// Message = notification.NotificationBody
// };
// SendMessageUsingFCM(notification.NotificationTitle, template, notification.FCMTopic, notification.NotificationBody, response.FCMConfiguration);
// WriteToFile("Notifications Sent To Topic : " + notification.FCMTopic, "SendFCMNotificationAndReminders");
// dal.MarkFCMNotificationSent(notification.Id);
// WriteToFile("Notifications Mark as Sent in DB Id : " + notification.Id, "SendFCMNotificationAndReminders");
//}
}
What It Does:
- Currently Disabled: Code is commented out
- Original Purpose: Send general FCM notifications
- Notification Type 11: Common/general notifications
Why Commented Out:
- Notifications likely handled by PsyterAPI instead
- WindowsService focuses on payment-related operations
- Reduces duplicate notification sending
Step 3: Process Appointment Reminders¶
if(response.UserRemindersList.Count > 0)
{
foreach (var reminder in response.UserRemindersList)
{
What It Does:
- Iterates through pending reminders
- Each reminder is for a specific user and booking
- Sends reminder notification via PsyterAPI
Step 4a: Process Patient Reminders (UserType = 1)¶
if (reminder.UserType == 1)
{
var userRemindersList = dal.GetUserRemindersList(reminder.PhysicianId, reminder.SlotBookingId);
object template = new
{
ClientUserLoginInfoId = reminder.PatientId,
ClientFirstNamePLang = reminder.PatientFirstNamePLang,
ClientLastNamePLang = reminder.PatientLastNamePLang,
ClientFirstNameSLang = reminder.PatientFirstNameSLang,
ClientLastNameSLang = reminder.PatientLastNameSLang,
NotificationType = 12, //Booking reminder
Message = reminder.ReminderTextPLang,
MessageSLang = reminder.ReminderTextSLang,
VideoSDKMeetingId = reminder.VideoSDKMeetingId,
SlotBookingId = reminder.SlotBookingId,
ReminderBeforeMinutes = reminder.ReminderBeforeMinutes,
SlotDateTimeUTC = reminder.SlotDatetimeUTC,
UserType = 1,
CatCommunicationTypeId = reminder.CatCommunicationTypeId,
RemindersList = userRemindersList
};
SendCustomFCMNotification notificationRequest = new SendCustomFCMNotification();
notificationRequest.Title = reminder.ReminderTitle;
notificationRequest.Body = reminder.ReminderTextPLang;
notificationRequest.Topic = reminder.FCMTopic;
notificationRequest.Template = template;
CallSendFCMNotificationCommonAPI("SendFCMNotificationAndReminders", JsonConvert.SerializeObject(notificationRequest));
}
What It Does:
UserType = 1: Patient/Client reminder
Get User’s Reminder List:
var userRemindersList = dal.GetUserRemindersList(reminder.PhysicianId, reminder.SlotBookingId);
- Retrieves all reminders for physician and this booking
- Used to show patient all upcoming reminders
Build Notification Template:
- ClientUserLoginInfoId: Patient ID
- Client Names: Patient name in both languages (PLang = Primary, SLang = Secondary)
- NotificationType: 12 (Booking reminder)
- Message/MessageSLang: Reminder text in both languages
- VideoSDKMeetingId: Video call meeting ID (if video consultation)
- SlotBookingId: Booking identifier
- ReminderBeforeMinutes: How many minutes before appointment (e.g., 15, 60, 1440)
- SlotDateTimeUTC: Appointment date/time
- UserType: 1 (Patient)
- CatCommunicationTypeId: Communication type (in-clinic, video, phone)
- RemindersList: All reminders for this booking
Create FCM Notification Request:
SendCustomFCMNotification notificationRequest = new SendCustomFCMNotification();
notificationRequest.Title = reminder.ReminderTitle;
notificationRequest.Body = reminder.ReminderTextPLang;
notificationRequest.Topic = reminder.FCMTopic;
notificationRequest.Template = template;
FCM Topic Structure:
- Format: /topics/user_{userId}_{platform}
- Example: /topics/user_98765_android
- Enables targeted delivery to specific user’s device
Send Notification:
CallSendFCMNotificationCommonAPI("SendFCMNotificationAndReminders", JsonConvert.SerializeObject(notificationRequest));
Step 4b: Process Physician Reminders (UserType = 0)¶
if (reminder.UserType == 0)
{
var userRemindersList = dal.GetUserRemindersList(reminder.PatientId, reminder.SlotBookingId);
object template = new
{
ProviderUserLoginInfoId = reminder.PhysicianId,
ProviderFirstNamePLang = reminder.PhysicianFirstNamePLang,
ProviderLastNamePLang = reminder.PhysicianLastNamePLang,
ProviderFirstNameSLang = reminder.PhysicianFirstNameSLang,
ProviderLastNameSLang = reminder.PhysicianLastNameSLang,
NotificationType = 12, //Booking reminder
Message = reminder.ReminderTextPLang,
MessageSLang = reminder.ReminderTextSLang,
VideoSDKMeetingId = reminder.VideoSDKMeetingId,
SlotBookingId = reminder.SlotBookingId,
ReminderBeforeMinutes = reminder.ReminderBeforeMinutes,
SlotDateTimeUTC = reminder.SlotDatetimeUTC,
UserType = 0,
CatCommunicationTypeId = reminder.CatCommunicationTypeId,
RemindersList = userRemindersList
};
SendCustomFCMNotification notificationRequest = new SendCustomFCMNotification();
notificationRequest.Title = reminder.ReminderTitle;
notificationRequest.Body = reminder.ReminderTextPLang;
notificationRequest.Topic = reminder.FCMTopic;
notificationRequest.Template = template;
CallSendFCMNotificationCommonAPI("SendFCMNotificationAndReminders", JsonConvert.SerializeObject(notificationRequest));
}
What It Does:
UserType = 0: Physician/Care Provider reminder
Key Differences from Patient Reminder:
- Uses Provider fields instead of Client fields
- Gets reminder list for Patient (physician sees patient info)
- FCM topic targets physician’s device
Physician Reminder Use Case:
- Reminds physician of upcoming appointments
- Shows patient information
- Displays appointment details and meeting link
Step 5: Mark Reminder as Sent¶
WriteToFile("Reminder Sent To Topic : " + reminder.FCMTopic, "SendFCMNotificationAndReminders");
dal.MarkReminderSent(reminder.Id, reminder.UserLoginInfoId, reminder.ReminderTitle, reminder.ReminderTitleSLang, reminder.ReminderTextPLang, reminder.ReminderTextSLang);
WriteToFile("Reminder Mark as Sent in DB Id : " + reminder.Id, "SendFCMNotificationAndReminders");
What It Does:
1. Logs reminder sent to topic
2. Calls DAL method to mark reminder as sent:
- Updates reminder status
- Creates notification history record
- Records sent timestamp
3. Logs database update completion
Database Operations:
- Reminder table: Mark as sent
- Notification table: Create history record
- Prevents duplicate reminder sending
Exception Handling¶
catch (Exception ex)
{
WriteToFile("Exception occur " + DateTime.Now + ", Error: " + ex.Message, "SendFCMNotification");
WriteToFile("-----------------------------END--------------------------------", "SendFCMNotification");
}
What It Does:
- Catches any exceptions during FCM processing
- Logs exception with timestamp
- Service continues running (doesn’t crash)
2. Call Send FCM Notification Common API (CallSendFCMNotificationCommonAPI)¶
Method Overview¶
public async Task CallSendFCMNotificationCommonAPI(string callFrom, string requestData)
Purpose: Send FCM notification request to PsyterAPI (centralized notification handler)
Parameters:
- callFrom: Log context (e.g., “SendFCMNotificationAndReminders”)
- requestData: JSON string containing FCM notification request
Code Analysis¶
WriteToFile("CallSendFCMNotificationCommonAPI called " + DateTime.Now, callFrom);
PaymentDataAccess dal = new PaymentDataAccess();
string applicationToken = dal.GetPsyterApplicationToken();
if (!string.IsNullOrEmpty(applicationToken))
{
WriteToFile("Psyter API Call Token Created " + DateTime.Now, callFrom);
var authResponse = await PsyterApiAuthenticationToken(applicationToken);
var content = new StringContent(requestData, Encoding.UTF8, "application/json");
HttpClient mObjHttpClient = new HttpClient();
mObjHttpClient.BaseAddress = new Uri(ConfigurationManager.AppSettings["PsyterAPIBaseURL"]);
mObjHttpClient.DefaultRequestHeaders.Clear();
mObjHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
mObjHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResponse.AccessToken);
HttpResponseMessage responseMessage = await mObjHttpClient.PostAsync("Notification/SendNotificationWithCustomData", content);
var responseJson = await responseMessage.Content.ReadAsStringAsync();
WriteToFile("Call Ended at " + DateTime.Now, callFrom);
WriteToFile("-----------------------------END--------------------------------", callFrom);
}
What It Does:
Step 1: Log API Call Start¶
WriteToFile("CallSendFCMNotificationCommonAPI called " + DateTime.Now, callFrom);
Step 2: Get Application Token from Database¶
PaymentDataAccess dal = new PaymentDataAccess();
string applicationToken = dal.GetPsyterApplicationToken();
What It Does:
- Retrieves PsyterAPI application token from database
- Token used for OAuth authentication
Step 3: Authenticate with PsyterAPI¶
var authResponse = await PsyterApiAuthenticationToken(applicationToken);
What It Does:
- Calls authentication method
- Gets OAuth bearer token
- Token valid for 24 hours
Step 4: Prepare HTTP Request¶
var content = new StringContent(requestData, Encoding.UTF8, "application/json");
HttpClient mObjHttpClient = new HttpClient();
mObjHttpClient.BaseAddress = new Uri(ConfigurationManager.AppSettings["PsyterAPIBaseURL"]);
mObjHttpClient.DefaultRequestHeaders.Clear();
mObjHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
mObjHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResponse.AccessToken);
HTTP Configuration:
- Content-Type: application/json
- Accept: application/json
- Authorization: Bearer token
Step 5: Send FCM Notification Request¶
HttpResponseMessage responseMessage = await mObjHttpClient.PostAsync("Notification/SendNotificationWithCustomData", content);
var responseJson = await responseMessage.Content.ReadAsStringAsync();
API Endpoint:
POST https://dvx.innotech-sa.com/Psyter/Master/APIs/Notification/SendNotificationWithCustomData
Request Body Example:
{
"Title": "Appointment Reminder",
"Body": "You have an appointment in 15 minutes",
"Topic": "/topics/user_98765_android",
"Template": {
"ClientUserLoginInfoId": 98765,
"ClientFirstNamePLang": "محمد",
"ClientLastNamePLang": "أحمد",
"NotificationType": 12,
"Message": "لديك موعد بعد 15 دقيقة",
"VideoSDKMeetingId": "meeting_12345",
"SlotBookingId": 12345,
"ReminderBeforeMinutes": 15,
"SlotDateTimeUTC": "2025-11-05T10:30:00Z",
"RemindersList": [...]
}
}
What PsyterAPI Does:
1. Validates authentication token
2. Constructs FCM message payload
3. Sends notification to Firebase Cloud Messaging
4. Firebase delivers notification to user’s device
5. Returns success/failure response
Step 6: Log Completion¶
WriteToFile("Call Ended at " + DateTime.Now, callFrom);
WriteToFile("-----------------------------END--------------------------------", callFrom);
What It Does:
- Logs API call completion timestamp
- Marks end of FCM notification process
Supporting Features¶
1. Call Notify SCHFS Expiry API (CallNotifySCHFSExpiryAPI)¶
Method Overview¶
public async Task CallNotifySCHFSExpiryAPI()
Purpose: Notify users about Saudi Council for Health Specialties (SCHFS) card expiry
Execution Frequency: Every 24 hours (timer interval: 86,400,000 ms)
SCHFS Background:
- SCHFS: Saudi Council for Health Specialties
- Care providers must have valid SCHFS credentials
- Cards expire periodically and must be renewed
- System tracks expiry dates and sends reminders
Code Analysis¶
WriteToFile("CallNotifySCHFSExpiryAPI called " + DateTime.Now, "NotifySCHFSCardExpiry");
PaymentDataAccess dal = new PaymentDataAccess();
string applicationToken = dal.GetPsyterApplicationToken();
if (!string.IsNullOrEmpty(applicationToken))
{
WriteToFile("Psyter API Call Token Created " + DateTime.Now, "NotifySCHFSCardExpiry");
var authResponse = await PsyterApiAuthenticationToken(applicationToken);
HttpClient mObjHttpClient = new HttpClient();
mObjHttpClient.BaseAddress = new Uri(ConfigurationManager.AppSettings["PsyterAPIBaseURL"]);
mObjHttpClient.DefaultRequestHeaders.Clear();
mObjHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
mObjHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResponse.AccessToken);
HttpResponseMessage responseMessage = await mObjHttpClient.GetAsync("Notification/NotifySCHFSCardExpiry");
var responseJson = await responseMessage.Content.ReadAsStringAsync();
WriteToFile("Notify API Response: " + responseJson, "NotifySCHFSCardExpiry");
WriteToFile("Call Ended at " + DateTime.Now, "NotifySCHFSCardExpiry");
WriteToFile("-----------------------------END--------------------------------", "NotifySCHFSCardExpiry");
}
What It Does:
Step 1: Log API Call¶
WriteToFile("CallNotifySCHFSExpiryAPI called " + DateTime.Now, "NotifySCHFSCardExpiry");
Step 2: Get Application Token and Authenticate¶
string applicationToken = dal.GetPsyterApplicationToken();
var authResponse = await PsyterApiAuthenticationToken(applicationToken);
Step 3: Configure HTTP Client¶
HttpClient mObjHttpClient = new HttpClient();
mObjHttpClient.BaseAddress = new Uri(ConfigurationManager.AppSettings["PsyterAPIBaseURL"]);
mObjHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResponse.AccessToken);
Step 4: Call SCHFS Expiry Notification API¶
HttpResponseMessage responseMessage = await mObjHttpClient.GetAsync("Notification/NotifySCHFSCardExpiry");
var responseJson = await responseMessage.Content.ReadAsStringAsync();
API Endpoint:
GET https://dvx.innotech-sa.com/Psyter/Master/APIs/Notification/NotifySCHFSCardExpiry
What PsyterAPI Does:
1. Queries database for physicians with expiring SCHFS cards
2. Checks cards expiring in: 30 days, 15 days, 7 days, 1 day
3. Sends notification to each physician
4. Sends notification to system administrators
5. Returns list of notified physicians
Notification Content (Physician):
Subject: SCHFS Card Expiry Reminder
Dear Dr. [Physician Name],
Your Saudi Council for Health Specialties (SCHFS) card is expiring soon.
Expiry Date: [Date]
Days Remaining: [X] days
Please renew your SCHFS card to continue providing services.
Steps to renew:
1. Visit SCHFS portal
2. Submit renewal application
3. Upload required documents
4. Update card information in Psyter
Thank you.
Step 5: Log Response¶
WriteToFile("Notify API Response: " + responseJson, "NotifySCHFSCardExpiry");
WriteToFile("Call Ended at " + DateTime.Now, "NotifySCHFSCardExpiry");
Response Example:
{
"reason": 1,
"data": {
"notificationsSent": 5,
"physicians": [
{ "physicianId": 123, "expiryDate": "2025-12-05", "daysRemaining": 30 },
{ "physicianId": 456, "expiryDate": "2025-11-20", "daysRemaining": 15 }
]
}
}
2. Delete Log Files of Previous Month (DeleteLogFilesOfPreviousMonth)¶
Method Overview¶
public void DeleteLogFilesOfPreviousMonth()
Purpose: Delete log files older than 30 days to prevent disk space issues
Execution Frequency: Every 15 days (timer interval: 1,296,000,000 ms)
Code Analysis¶
string path = AppDomain.CurrentDomain.BaseDirectory + "\\Logs";
string[] files = Directory.GetFiles(path);
WriteToFile("Files Count : " + files.Count(), "DeleteFileLog");
foreach (string file in files)
{
FileInfo fi = new FileInfo(file);
if (fi.CreationTime < DateTime.Now.AddDays(-30) && !fi.FullName.Contains("DeleteFileLog"))
{
fi.Delete();
}
WriteToFile("File Deleted : " + fi.FullName, "DeleteFileLog");
}
WriteToFile("-----------------------------END--------------------------------", "DeleteFileLog");
What It Does:
Step 1: Get Logs Directory Path¶
string path = AppDomain.CurrentDomain.BaseDirectory + "\\Logs";
Path Example:
C:\Program Files\PsyterPaymentInquiry\Logs\
Step 2: Get All Log Files¶
string[] files = Directory.GetFiles(path);
WriteToFile("Files Count : " + files.Count(), "DeleteFileLog");
Log Files Example:
ServiceLog_Inquiry_10_01_2025.txt
ServiceLog_Inquiry_10_02_2025.txt
ServiceLog_Refund_10_01_2025.txt
ServiceLog_SendFCMNotificationAndReminders_10_01_2025.txt
...
Step 3: Delete Old Files¶
foreach (string file in files)
{
FileInfo fi = new FileInfo(file);
if (fi.CreationTime < DateTime.Now.AddDays(-30) && !fi.FullName.Contains("DeleteFileLog"))
{
fi.Delete();
}
WriteToFile("File Deleted : " + fi.FullName, "DeleteFileLog");
}
Deletion Criteria:
1. File Age: Creation time older than 30 days
2. File Type: Not the “DeleteFileLog” file itself
Why 30 Days:
- Balances disk space and troubleshooting needs
- Sufficient time to investigate recent issues
- Prevents log directory from growing indefinitely
Self-Protection:
!fi.FullName.Contains("DeleteFileLog")
- Prevents deletion of current cleanup log file
- Ensures cleanup operation is always logged
Step 4: Log Deletion¶
WriteToFile("File Deleted : " + fi.FullName, "DeleteFileLog");
Log Output Example:
Files Count : 45
File Deleted : C:\Program Files\PsyterPaymentInquiry\Logs\ServiceLog_Inquiry_09_01_2025.txt
File Deleted : C:\Program Files\PsyterPaymentInquiry\Logs\ServiceLog_Refund_09_01_2025.txt
...
-----------------------------END--------------------------------
Data Transfer Objects (DTO)¶
The DTO folder contains model classes for data transfer between service layers, database, and APIs.
1. AppConfigSetting.cs¶
File: WindowsService/PsyterPaymentInquiry/DTO/AppConfigSetting.cs
Code Analysis¶
public class ApplicationConfiguration
{
public long Id { get; set; }
public long CatAppConfigGroupId { get; set; }
public string TitlePLang { get; set; }
public string TitleSLang { get; set; }
public string PropertyValue { get; set; }
}
public class PaymentApplicationConfiguration
{
public string SmartRoutingSecretKey { get; set; }
public string SmartRoutingResponseBackURL { get; set; }
public string RedirectToPaymentMobileToken { get; set; }
public string SmartRoutingRedirectURL { get; set; }
public string SmartRoutingMerchantId { get; set; }
public string SmartRoutingCurrencyISOCode { get; set; }
public string SmartRoutingRefundInquiryUrl { get; set; }
public string SmartRoutingThemeId { get; set; }
public string SmartRoutingVersion { get; set; }
public string SmartRoutingItemId { get; set; }
}
Purpose:
ApplicationConfiguration:
- Represents a single configuration setting from database
- Id: Configuration ID
- CatAppConfigGroupId: Configuration group (e.g., 1 = General, 2 = Payment Gateway)
- TitlePLang/TitleSLang: Configuration name in both languages
- PropertyValue: Configuration value
Example Records:
Id: 25, TitlePLang: "SmartRoutingSecretKey", PropertyValue: "abc123xyz..."
Id: 26, TitlePLang: "SmartRoutingMerchantId", PropertyValue: "12345"
Id: 30, TitlePLang: "PsyterAPIApplicationToken", PropertyValue: "token123..."
PaymentApplicationConfiguration:
- Typed configuration object for payment gateway settings
- All properties populated from ApplicationConfiguration list
- Used throughout payment processing workflow
Usage:
// Retrieve from database
var appConfigList = dal.GETPendingPayments().AppConfigSettingList;
// Convert to typed object
var paymentConfig = new PaymentApplicationConfiguration();
paymentConfig.SmartRoutingSecretKey = appConfigList.FirstOrDefault(s => s.TitlePLang == "SmartRoutingSecretKey")?.PropertyValue;
// ... populate other properties
// Use in payment processing
var hash = GenerateSecureHash(request, paymentConfig);
2. FCMNotificationModal.cs¶
File: WindowsService/PsyterPaymentInquiry/DTO/FCMNotificationModal.cs
Code Analysis¶
public class UserNotification
{
public long Id { get; set; }
public long ForUserLoginInfoId { get; set; }
public string NotificationTitle { get; set; }
public string NotificationBody { get; set; }
public string FCMTopic { get; set; }
}
Purpose: General user notification (currently not used by WindowsService)
Properties:
- Id: Notification ID
- ForUserLoginInfoId: Target user ID
- NotificationTitle: Notification title
- NotificationBody: Notification message
- FCMTopic: FCM topic for delivery (e.g., /topics/user_98765_android)
public class UserReminder
{
public long Id { get; set; }
public long OrderMainId { get; set; }
public long SlotBookingId { get; set; }
public long UserLoginInfoId { get; set; }
public int UserType { get; set; }
public string ReminderTitle { get; set; }
public string ReminderTitleSLang { get; set; }
public string ReminderTextPLang { get; set; }
public string ReminderTextSLang { get; set; }
public string FCMTopic { get; set; }
public string VideoSDKMeetingId { get; set; }
public int ReminderBeforeMinutes { get; set; }
public DateTime SlotDatetimeUTC { get; set; }
public DateTime AppointmentDatetimeUTC { get; set; }
public DateTime ReminderDatetime { get; set; }
public long PatientId { get; set; }
public string PatientFirstNamePLang { get; set; }
public string PatientMiddleNamePLang { get; set; }
public string PatientLastNamePLang { get; set; }
public string PatientFirstNameSLang { get; set; }
public string PatientMiddleNameSLang { get; set; }
public string PatientLastNameSLang { get; set; }
public string PatientImagePath { get; set; }
public long PhysicianId { get; set; }
public string PhysicianFirstNamePLang { get; set; }
public string PhysicianMiddleNamePLang { get; set; }
public string PhysicianLastNamePLang { get; set; }
public string PhysicianFirstNameSLang { get; set; }
public string PhysicianMiddleNameSLang { get; set; }
public string PhysicianLastNameSLang { get; set; }
public string PhysicianImagePath { get; set; }
public int CatCommunicationTypeId { get; set; }
}
Purpose: Appointment reminder details
Key Properties:
- UserType: 0 = Physician, 1 = Patient
- ReminderBeforeMinutes: Reminder timing (5, 15, 60, 1440 minutes)
- Bilingual Support: All text fields in PLang (Primary) and SLang (Secondary)
- Patient/Physician Info: Names and IDs for both parties
- VideoSDKMeetingId: Video call meeting identifier
- CatCommunicationTypeId: Communication type (1 = In-clinic, 2 = Video, 3 = Phone)
public class UserReminderForFCMPayLoad
{
public long Id { get; set; }
public string ReminderTitle { get; set; }
public string ReminderTitleSLang { get; set; }
public string ReminderTextPLang { get; set; }
public string ReminderTextSLang { get; set; }
public int ReminderBeforeMinutes { get; set; }
public DateTime ReminderDatetime { get; set; }
}
Purpose: Simplified reminder for FCM payload (list of all reminders for booking)
public class FCMConfiguration
{
public string FCMAPIUrl { get; set; }
public string FCMAuthorizationKey { get; set; }
public string FCMSenderId { get; set; }
}
Purpose: Firebase Cloud Messaging configuration (not currently used - notifications sent via PsyterAPI instead)
public class UserNotificationsListWrapper
{
public List<UserNotification> UserNotificationsList { get; set; }
public FCMConfiguration FCMConfiguration { get; set; }
public List<UserReminder> UserRemindersList { get; set; }
}
Purpose: Wrapper for all notification data retrieved from database
public class SendCustomFCMNotification
{
public string Title { get; set; }
public string Body { get; set; }
public string Topic { get; set; }
public object Template { get; set; }
}
Purpose: FCM notification request sent to PsyterAPI
Properties:
- Title: Notification title
- Body: Notification body text
- Topic: FCM topic (e.g., /topics/user_98765_android)
- Template: Custom data object (embedded in notification)
3. InquiryPayment.cs¶
File: WindowsService/PsyterPaymentInquiry/DTO/InquiryPayment.cs
Code Analysis¶
public class RequestRefund
{
public long ORDER_ID { get; set; }
public string TRANSACTION_ID { get; set; }
public string AMOUNT { get; set; }
}
Purpose: Refund request data (not actively used - refund logic in other classes)
public class RequestSecureHash
{
public string SECRECT_KEY { get; set; }
public string MESSAGE_ID { get; set; }
public string TRANSACTION_ID { get; set; }
public string AMOUNT { get; set; }
public bool IsRefundInquiry { get; set; }
}
Purpose: Input parameters for secure hash generation
Properties:
- SECRECT_KEY: Payment gateway shared secret
- MESSAGE_ID: “2” = Inquiry, “4” = Refund
- TRANSACTION_ID: Transaction to inquire/refund
- AMOUNT: Refund amount (required for refunds)
- IsRefundInquiry: true = Inquiry about refund status, false = New refund request
public class SecureHashResponse
{
public string SECURE_HASH { get; set; }
public string TRANSACTION_ID { get; set; }
}
Purpose: Output from secure hash generation
Properties:
- SECURE_HASH: SHA256 hash (64-character hex string)
- TRANSACTION_ID: Newly generated transaction ID (for refunds)
public class RequestProcessInquiry
{
public string SECRECT_KEY { get; set; }
public string MESSAGE_ID { get; set; }
public string TRANSACTION_ID { get; set; }
public string AMOUNT { get; set; }
public string HASH_TRANSACTION_ID { get; set; }
public string SECURE_HASH { get; set; }
public long ORDER_ID { get; set; }
public long PAYFORDATA_ID { get; set; }
public long USER_WALLET_ID { get; set; }
public long CLIENT_PACKAGE_MAIN_ID { get; set; }
}
Purpose: Complete inquiry/refund request sent to payment gateway
Properties:
- Gateway Parameters: SECRECT_KEY, MESSAGE_ID, TRANSACTION_ID, AMOUNT, SECURE_HASH
- Database Identifiers: ORDER_ID, PAYFORDATA_ID, USER_WALLET_ID, CLIENT_PACKAGE_MAIN_ID
- HASH_TRANSACTION_ID: New transaction ID for refunds
public class ProcessResponse
{
public string RESPONSE_DATA { get; set; }
public string STATUS_CODE { get; set; }
public string STATUS_MESSAGE { get; set; }
public bool STATUS { get; set; }
}
Purpose: Payment gateway response (generic wrapper - not actively used)
public class UpdateScheduleBookingStatusRequest
{
public List<UpdateBookingStatus> BookingStatusUpdateInfoList { get; set; }
}
public class UpdateBookingStatus
{
public long BookingId { get; set; }
public int NewBookingStatusId { get; set; }
public DateTime StatusUpdateDateTime { get; set; }
public int StatusUpdateAuthority { get; set; }
public long UpdatedBy { get; set; }
public long CancelledBy { get; set; }
}
Purpose: Booking status update request sent to SchedulingAPI
UpdateBookingStatus Properties:
- BookingId: Booking to update
- NewBookingStatusId: New status (1 = Booked, 8 = Cancelled)
- StatusUpdateDateTime: UTC timestamp
- StatusUpdateAuthority: 2 = System/Automated
- UpdatedBy: User ID who triggered update
- CancelledBy: User ID who cancelled (if applicable)
public class BookingStatusUpdateResponseWrapper
{
public BookingStatusUpdateResponse data { get; set; }
public int reason { get; set; }
}
public class BookingStatusUpdateResponse
{
public List<BookingStatusUpdateInfoResponse> BookingStatusUpdateInfoList { get; set; }
}
public class BookingStatusUpdateInfoResponse
{
public long BookingId { get; set; }
public long NewBookingStatusId { get; set; }
public long? CancelledBy { get; set; }
public DateTime? StatusUpdateDateTime { get; set; }
public int? StatusUpdateAuthority { get; set; }
public string RejectCode { get; set; }
public string RejectReason { get; set; }
}
Purpose: Booking status update response from SchedulingAPI
Response Fields:
- reason: 1 = Success, 0 = Failure
- RejectCode: Error code if update failed (“7” = Already in requested status)
- RejectReason: Human-readable error message
public class ApplicationConfiguration
{
public long Id { get; set; }
public long CatAppConfigGroupId { get; set; }
public string TitlePLang { get; set; }
public string TitleSLang { get; set; }
public string PropertyValue { get; set; }
}
Purpose: Duplicate of ApplicationConfiguration from AppConfigSetting.cs (for namespace separation)
4. PendingPaymentModal.cs¶
File: WindowsService/PsyterPaymentInquiry/DTO/PendingPaymentModal.cs
Code Analysis¶
public class GetPendingPaymentsListResponse
{
public List<PendingPayment> PendingPaymentsList { get; set; }
public List<ApplicationConfiguration> AppConfigSettingList { get; set; }
public string ApplicationAPIToken { get; set; }
}
Purpose: Response from database query for pending payments
Properties:
- PendingPaymentsList: List of pending payment records
- AppConfigSettingList: Payment gateway configuration
- ApplicationAPIToken: API authentication token (for SchedulingAPI or PsyterAPI)
public class PendingPaymentResponse
{
public List<PendingPayment> PendingPaymentsList { get; set; }
public PaymentApplicationConfiguration AppConfigSetting { get; set; }
}
Purpose: Processed pending payments with typed configuration object
public class PendingPayment
{
public string TransactionId { get; set; }
public long OrderId { get; set; }
public long PayForDataId { get; set; }
public long SlotBookingId { get; set; }
public long ConsumerId { get; set; }
public string ConsumerFullName { get; set; }
public long PhysicianId { get; set; }
public string PhysicianFullName { get; set; }
public string TotalAmount { get; set; }
public decimal PhysicianCharges { get; set; }
public decimal SystemCharges { get; set; }
public string PaymentStatus { get; set; }
public long UserWalletId { get; set; }
public int InquiryCount { get; set; }
public long ClientPackageMainId { get; set; }
}
Purpose: Pending payment record from database
Properties:
- TransactionId: Payment gateway transaction ID
- OrderId: Booking order ID (0 for wallet/package)
- PayForDataId: Payment record ID
- SlotBookingId: Booking slot ID
- ConsumerId/PhysicianId: User IDs
- TotalAmount: Payment amount
- PhysicianCharges/SystemCharges: Fee breakdown
- PaymentStatus: “Pending”, “Pending For Refund”, “Pending Refund”
- UserWalletId: Wallet ID (for wallet purchases)
- InquiryCount: Number of inquiry attempts (max 3)
- ClientPackageMainId: Package ID (for package purchases)
public class UpdateBookingOrderPayForData
{
public long OrderId { get; set; }
public long PayForDataId { get; set; }
public string TransactionID { get; set; }
public string CardHolderName { get; set; }
public string CardNumber { get; set; }
public string CardExpiryDate { get; set; }
public string GatewayName { get; set; }
public string GatewayStatusCode { get; set; }
public string GatewayStatusDescription { get; set; }
public string RRN { get; set; }
public string StatusCode { get; set; }
public string StatusDescription { get; set; }
public long UserWalletId { get; set; }
public long ClientPackageMainId { get; set; }
}
Purpose: Payment status update data (sent to stored procedure as XML)
Properties:
- Identifiers: OrderId, PayForDataId, UserWalletId, ClientPackageMainId
- Card Info: CardHolderName, CardNumber (masked), CardExpiryDate
- Gateway Response: GatewayName, GatewayStatusCode, GatewayStatusDescription
- Transaction Info: TransactionID, RRN (Retrieval Reference Number)
- Status: StatusCode (“00000” = Success), StatusDescription
public class UpdateBookingRefundRequestData
{
public long OrderId { get; set; }
public long PayForDataId { get; set; }
public string TransactionId { get; set; }
public string Amount { get; set; }
public string StatusCode { get; set; }
public string StatusDescription { get; set; }
}
Purpose: Refund status update data (sent to stored procedure as XML)
Properties:
- Identifiers: OrderId, PayForDataId
- Refund Info: TransactionId (refund transaction), Amount
- Status: StatusCode, StatusDescription
5. PsyterAPIAuth.cs¶
File: WindowsService/PsyterPaymentInquiry/DTO/PsyterAPIAuth.cs
Code Analysis¶
public class APIAuthTokenResponse
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public string TokenExpiresIn { get; set; }
}
Purpose: OAuth authentication response from PsyterAPI or SchedulingAPI
Properties:
- AccessToken: JWT bearer token (used in Authorization header)
- RefreshToken: Token to refresh access token
- TokenExpiresIn: Token lifetime in seconds (e.g., “86400” = 24 hours)
Usage:
var authResponse = await PsyterApiAuthenticationToken(applicationToken);
string bearerToken = authResponse.AccessToken;
// Use in API calls
mObjHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
public class RefundBookingNotificationRequest
{
public long BookingId { get; set; }
public long CareProviderId { get; set; }
public long PatientId { get; set; }
public bool NoNeedToCreateLog { get; set; }
}
Purpose: Refund notification request sent to PsyterAPI
Properties:
- BookingId: Booking that was refunded
- CareProviderId: Physician user ID
- PatientId: Patient user ID
- NoNeedToCreateLog: true (notification already logged in payment system)
Usage:
RefundBookingNotificationRequest request = new RefundBookingNotificationRequest();
request.BookingId = 12345;
request.CareProviderId = 56789;
request.PatientId = 98765;
request.NoNeedToCreateLog = true;
await CallRefundNotificationAPI(JsonConvert.SerializeObject(request));
Deployment - Advanced Installer Project¶
Location: WindowsService/Psyter Payment Inquiry Service/
Files¶
-
Psyter Payment Inquiry Service.aip
- Advanced Installer project file
- Defines installation configuration
- Creates MSI installer package -
Setup Files/
- Installation resources and files
- Service executable and dependencies
- Configuration templates
Advanced Installer Configuration¶
Project Settings:
- Product Name: Psyter Payment Inquiry Service
- Version: Tracked in .aip file
- Publisher: Innotech (assumed)
- Install Location: C:\Program Files\PsyterPaymentInquiry\
Installation Actions:
1. Copy Files:
- PsyterPaymentInquiry.exe
- App.config
- DLL dependencies (Enterprise Library, Newtonsoft.Json)
-
Install Windows Service:
- Service Name: PsyterPaymentInquiry
- Display Name: Psyter Payment Inquiry Service
- Description: Automated payment gateway inquiry and refund processing
- Start Type: Automatic
- Account: Local System (or custom service account) -
Create Logs Directory:
-C:\Program Files\PsyterPaymentInquiry\Logs\
- Permissions: Service account has write access -
Configure Firewall:
- Allow outbound HTTPS connections (port 443)
- For payment gateway and API communication
Installation Steps¶
Prerequisites:
- Windows Server 2012 or later
- .NET Framework 4.8 installed
- SQL Server access configured
- Internet connectivity for API calls
Install Service:
1. Run MSI installer: Psyter Payment Inquiry Service.msi
2. Follow installation wizard
3. Configure database connection string in App.config
4. Configure API URLs in App.config
5. Start service via Services console or:
Start-Service PsyterPaymentInquiry
Verify Installation:
# Check service status
Get-Service PsyterPaymentInquiry
# Check logs
Get-Content "C:\Program Files\PsyterPaymentInquiry\Logs\ServiceLog_Inquiry_*.txt" -Tail 50
Uninstall Service:
1. Stop service:
Stop-Service PsyterPaymentInquiry
2. Run uninstaller from Control Panel → Programs and Features
3. Or run:
msiexec /x {ProductCode}
Service Management¶
Start Service:
Start-Service PsyterPaymentInquiry
Stop Service:
Stop-Service PsyterPaymentInquiry
Restart Service:
Restart-Service PsyterPaymentInquiry
Check Service Status:
Get-Service PsyterPaymentInquiry | Format-List *
View Service Logs:
# Recent inquiry logs
Get-Content "C:\Program Files\PsyterPaymentInquiry\Logs\ServiceLog_Inquiry_*.txt" -Tail 100
# Recent refund logs
Get-Content "C:\Program Files\PsyterPaymentInquiry\Logs\ServiceLog_Refund_*.txt" -Tail 100
# Recent FCM notification logs
Get-Content "C:\Program Files\PsyterPaymentInquiry\Logs\ServiceLog_SendFCMNotificationAndReminders_*.txt" -Tail 100
Configuration Management¶
Update Database Connection:
1. Stop service
2. Edit C:\Program Files\PsyterPaymentInquiry\PsyterPaymentInquiry.exe.config
3. Update connection string:
<connectionStrings>
<add name="PsyterDatabase"
connectionString="Data Source=server;Initial Catalog=psyter_v1;User ID=user;Password=pass"
providerName="System.Data.SqlClient" />
</connectionStrings>
4. Start service
Update API URLs:
1. Stop service
2. Edit App.config:
<appSettings>
<add key="PsyterAPIBaseURL" value="https://api.example.com/Psyter/" />
<add key="SchedulingAPIBaseURL" value="https://api.example.com/Scheduling/" />
</appSettings>
3. Start service
Troubleshooting¶
Service Won’t Start:
1. Check Event Viewer → Windows Logs → Application
2. Verify database connectivity
3. Check App.config syntax
4. Verify service account permissions
No Logs Created:
1. Check Logs directory exists
2. Verify service account has write permissions
3. Check disk space
Payment Inquiries Not Processing:
1. Check inquiry logs for errors
2. Verify payment gateway URL accessible
3. Check database connection
4. Verify application token in database
Notifications Not Sending:
1. Check FCM notification logs
2. Verify PsyterAPI authentication
3. Check API URLs in App.config
4. Verify internet connectivity
Summary of Part 4¶
This document has covered:
✅ FCM Notification System
- GetPendingFCMNotificationsAndReminders() - Reminder retrieval and processing
- CallSendFCMNotificationCommonAPI() - Notification delivery via PsyterAPI
- Patient and physician reminder handling
- FCM topic-based targeting
✅ Supporting Features
- CallNotifySCHFSExpiryAPI() - SCHFS card expiry notifications
- DeleteLogFilesOfPreviousMonth() - Log file cleanup (30-day retention)
✅ Data Transfer Objects (DTO) - All 5 Files
- AppConfigSetting.cs - Configuration models
- FCMNotificationModal.cs - Notification and reminder models
- InquiryPayment.cs - Payment inquiry/refund models
- PendingPaymentModal.cs - Payment processing models
- PsyterAPIAuth.cs - API authentication models
✅ Deployment
- Advanced Installer project structure
- Installation steps and prerequisites
- Service management commands
- Configuration management
- Troubleshooting guide
WindowsService Documentation Complete¶
All 4 Parts Summary:
Part 1: Infrastructure, configuration, service lifecycle, timers, threads, logging, DAL data retrieval
Part 2: DAL update methods, XmlHelper, payment processing workflows, gateway integration
Part 3: Payment status updates, API authentication, booking API integration
Part 4: FCM notifications, supporting features, DTOs, deployment
See Separate Document: WINDOWSSERVICE_ARCHITECTURE_SUMMARY.md for complete system architecture, workflows, and component interactions.
End of Part 4 - WindowsService Documentation Complete