diff --git a/.gitignore b/.gitignore
index 29eb907..f67dd76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,88 @@
-bin/
-obj/
-/packages/
+# Build results
+[Bb]in/
+[Oo]bj/
+[Dd]ebug/
+[Rr]elease/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+
+# .NET / NuGet
+*.nupkg
+*.snupkg
+*.nuspec
+packages/
+project.lock.json
+project.assets.json
+artifacts/
+TestResults/
+coverage/
+coverage.*
+*.coverage
+*.coveragexml
+
+# User-specific files
+*.user
+*.rsuser
+*.suo
+*.userosscache
+*.sln.docstates
+*.DotSettings.user
+*.csproj.user
+
+# Visual Studio
+.vs/
+*.vssscc
+*.vspscc
+_ReSharper*/
+_ReSharper.Caches/
+*.resharper
+
+# Rider / JetBrains
+.idea/
+*.sln.iml
+*.iml
riderModule.iml
-/_ReSharper.Caches/
-appsettings.Development.json
-appsettings.json
\ No newline at end of file
+
+# VS Code
+.vscode/*
+!.vscode/extensions.json
+!.vscode/settings.example.json
+!.vscode/launch.example.json
+!.vscode/tasks.example.json
+
+# OS files
+.DS_Store
+Thumbs.db
+ehthumbs.db
+Desktop.ini
+
+# Logs
+*.log
+logs/
+Log/
+
+# Environment and secrets
+.env
+.env.*
+!.env.example
+secrets.json
+appsettings.json
+appsettings.*.json
+!appsettings.example.json
+!appsettings.*.example.json
+
+# Local databases
+*.db
+*.sqlite
+*.sqlite3
+*.mdf
+*.ldf
+
+# Docker local overrides
+docker-compose.override.yml
+
+# Publish output
+publish/
+wwwroot/dist/
diff --git a/.idea/.idea.Reservation_System/.idea/.gitignore b/.idea/.idea.Reservation_System/.idea/.gitignore
deleted file mode 100644
index a33dc03..0000000
--- a/.idea/.idea.Reservation_System/.idea/.gitignore
+++ /dev/null
@@ -1,15 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/projectSettingsUpdater.xml
-/modules.xml
-/.idea.Reservation_System.iml
-/contentModel.xml
-# Ignored default folder with query files
-/queries/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/.idea.Reservation_System/.idea/.name b/.idea/.idea.Reservation_System/.idea/.name
deleted file mode 100644
index 4317306..0000000
--- a/.idea/.idea.Reservation_System/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-Reservation_System
\ No newline at end of file
diff --git a/.idea/.idea.Reservation_System/.idea/indexLayout.xml b/.idea/.idea.Reservation_System/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163..0000000
--- a/.idea/.idea.Reservation_System/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.Reservation_System/.idea/vcs.xml b/.idea/.idea.Reservation_System/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/.idea.Reservation_System/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/config/applicationhost.config b/.idea/config/applicationhost.config
deleted file mode 100644
index 92370fd..0000000
--- a/.idea/config/applicationhost.config
+++ /dev/null
@@ -1,987 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.vs/ProjectEvaluation/reservation_system.metadata.v9.bin b/.vs/ProjectEvaluation/reservation_system.metadata.v9.bin
deleted file mode 100644
index 3d5ab46..0000000
Binary files a/.vs/ProjectEvaluation/reservation_system.metadata.v9.bin and /dev/null differ
diff --git a/.vs/ProjectEvaluation/reservation_system.projects.v9.bin b/.vs/ProjectEvaluation/reservation_system.projects.v9.bin
deleted file mode 100644
index b1e77a3..0000000
Binary files a/.vs/ProjectEvaluation/reservation_system.projects.v9.bin and /dev/null differ
diff --git a/.vs/ProjectEvaluation/reservation_system.strings.v9.bin b/.vs/ProjectEvaluation/reservation_system.strings.v9.bin
deleted file mode 100644
index 6a0aead..0000000
Binary files a/.vs/ProjectEvaluation/reservation_system.strings.v9.bin and /dev/null differ
diff --git a/.vs/Reservation_System/DesignTimeBuild/.dtbcache.v2 b/.vs/Reservation_System/DesignTimeBuild/.dtbcache.v2
deleted file mode 100644
index 1e05e56..0000000
Binary files a/.vs/Reservation_System/DesignTimeBuild/.dtbcache.v2 and /dev/null differ
diff --git a/.vs/Reservation_System/FileContentIndex/ae539560-ce46-4966-9da4-453ff7ce7541.vsidx b/.vs/Reservation_System/FileContentIndex/ae539560-ce46-4966-9da4-453ff7ce7541.vsidx
deleted file mode 100644
index 8b25003..0000000
Binary files a/.vs/Reservation_System/FileContentIndex/ae539560-ce46-4966-9da4-453ff7ce7541.vsidx and /dev/null differ
diff --git a/.vs/Reservation_System/v17/.futdcache.v2 b/.vs/Reservation_System/v17/.futdcache.v2
deleted file mode 100644
index b924ebf..0000000
Binary files a/.vs/Reservation_System/v17/.futdcache.v2 and /dev/null differ
diff --git a/.vs/Reservation_System/v17/.suo b/.vs/Reservation_System/v17/.suo
deleted file mode 100644
index c6e0ba6..0000000
Binary files a/.vs/Reservation_System/v17/.suo and /dev/null differ
diff --git a/.vs/Reservation_System/v17/DocumentLayout.backup.json b/.vs/Reservation_System/v17/DocumentLayout.backup.json
deleted file mode 100644
index bb1b617..0000000
--- a/.vs/Reservation_System/v17/DocumentLayout.backup.json
+++ /dev/null
@@ -1,173 +0,0 @@
-{
- "Version": 1,
- "WorkspaceRootPath": "E:\\Code\\c#\\Reservation_System\\",
- "Documents": [
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\services\\passwordhasherservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\services\\passwordhasherservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\application.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\application.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
- },
- {
- "AbsoluteMoniker": "D:0:0:{50003437-236E-486B-87C3-F06DAFE51795}|api\\api.csproj|e:\\code\\c#\\reservation_system\\api\\api.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|",
- "RelativeMoniker": "D:0:0:{50003437-236E-486B-87C3-F06DAFE51795}|api\\api.csproj|solutionrelative:api\\api.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\interfaces\\services\\iuserservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\interfaces\\services\\iuserservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\services\\appointmentservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\services\\appointmentservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\services\\userservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\services\\userservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\services\\serviceappservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\services\\serviceappservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{99CD1C44-45C7-476B-8C80-0A823954BA56}|Domain\\Domain.csproj|e:\\code\\c#\\reservation_system\\domain\\service.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{99CD1C44-45C7-476B-8C80-0A823954BA56}|Domain\\Domain.csproj|solutionrelative:domain\\service.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{99CD1C44-45C7-476B-8C80-0A823954BA56}|Domain\\Domain.csproj|e:\\code\\c#\\reservation_system\\domain\\domain.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|",
- "RelativeMoniker": "D:0:0:{99CD1C44-45C7-476B-8C80-0A823954BA56}|Domain\\Domain.csproj|solutionrelative:domain\\domain.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
- }
- ],
- "DocumentGroupContainers": [
- {
- "Orientation": 0,
- "VerticalTabListWidth": 256,
- "DocumentGroups": [
- {
- "DockedWidth": 200,
- "SelectedChildIndex": 4,
- "Children": [
- {
- "$type": "Document",
- "DocumentIndex": 2,
- "Title": "api.csproj",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\api\\api.csproj",
- "RelativeDocumentMoniker": "api\\api.csproj",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\api\\api.csproj",
- "RelativeToolTip": "api\\api.csproj",
- "ViewState": "AgIAAAEAAAAAAAAAAABBwBkAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
- "WhenOpened": "2026-02-04T12:40:09.071Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 3,
- "Title": "IUserService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Interfaces\\Services\\IUserService.cs",
- "RelativeDocumentMoniker": "Application\\Interfaces\\Services\\IUserService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Interfaces\\Services\\IUserService.cs",
- "RelativeToolTip": "Application\\Interfaces\\Services\\IUserService.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:38:00.905Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 4,
- "Title": "AppointmentService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\AppointmentService.cs",
- "RelativeDocumentMoniker": "Application\\Services\\AppointmentService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\AppointmentService.cs",
- "RelativeToolTip": "Application\\Services\\AppointmentService.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:37:56.288Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 5,
- "Title": "UserService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\UserService.cs",
- "RelativeDocumentMoniker": "Application\\Services\\UserService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\UserService.cs",
- "RelativeToolTip": "Application\\Services\\UserService.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:37:54.491Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 0,
- "Title": "PasswordHasherService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\PasswordHasherService.cs",
- "RelativeDocumentMoniker": "Application\\Services\\PasswordHasherService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\PasswordHasherService.cs*",
- "RelativeToolTip": "Application\\Services\\PasswordHasherService.cs*",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:37:40.758Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 7,
- "Title": "Service.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Domain\\Service.cs",
- "RelativeDocumentMoniker": "Domain\\Service.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Domain\\Service.cs",
- "RelativeToolTip": "Domain\\Service.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:37:25.567Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 6,
- "Title": "ServiceAppService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\ServiceAppService.cs",
- "RelativeDocumentMoniker": "Application\\Services\\ServiceAppService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\ServiceAppService.cs",
- "RelativeToolTip": "Application\\Services\\ServiceAppService.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAACEAAAAbAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:33:48.359Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 8,
- "Title": "Domain.csproj",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Domain\\Domain.csproj",
- "RelativeDocumentMoniker": "Domain\\Domain.csproj",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Domain\\Domain.csproj",
- "RelativeToolTip": "Domain\\Domain.csproj",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
- "WhenOpened": "2026-02-04T12:33:00.372Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 1,
- "Title": "Application.csproj",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Application.csproj",
- "RelativeDocumentMoniker": "Application\\Application.csproj",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Application.csproj",
- "RelativeToolTip": "Application\\Application.csproj",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAA0AAAAFAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
- "WhenOpened": "2026-02-04T12:32:48.335Z",
- "EditorCaption": ""
- }
- ]
- }
- ]
- }
- ]
-}
\ No newline at end of file
diff --git a/.vs/Reservation_System/v17/DocumentLayout.json b/.vs/Reservation_System/v17/DocumentLayout.json
deleted file mode 100644
index 6ddb446..0000000
--- a/.vs/Reservation_System/v17/DocumentLayout.json
+++ /dev/null
@@ -1,173 +0,0 @@
-{
- "Version": 1,
- "WorkspaceRootPath": "E:\\Code\\c#\\Reservation_System\\",
- "Documents": [
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\services\\passwordhasherservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\services\\passwordhasherservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\application.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\application.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
- },
- {
- "AbsoluteMoniker": "D:0:0:{50003437-236E-486B-87C3-F06DAFE51795}|api\\api.csproj|e:\\code\\c#\\reservation_system\\api\\api.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|",
- "RelativeMoniker": "D:0:0:{50003437-236E-486B-87C3-F06DAFE51795}|api\\api.csproj|solutionrelative:api\\api.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\interfaces\\services\\iuserservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\interfaces\\services\\iuserservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\services\\appointmentservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\services\\appointmentservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\services\\userservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\services\\userservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|e:\\code\\c#\\reservation_system\\application\\services\\serviceappservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{0BDB1E54-9822-4261-AF10-BE438DD36BDF}|Application\\Application.csproj|solutionrelative:application\\services\\serviceappservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{99CD1C44-45C7-476B-8C80-0A823954BA56}|Domain\\Domain.csproj|e:\\code\\c#\\reservation_system\\domain\\service.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{99CD1C44-45C7-476B-8C80-0A823954BA56}|Domain\\Domain.csproj|solutionrelative:domain\\service.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{99CD1C44-45C7-476B-8C80-0A823954BA56}|Domain\\Domain.csproj|e:\\code\\c#\\reservation_system\\domain\\domain.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|",
- "RelativeMoniker": "D:0:0:{99CD1C44-45C7-476B-8C80-0A823954BA56}|Domain\\Domain.csproj|solutionrelative:domain\\domain.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
- }
- ],
- "DocumentGroupContainers": [
- {
- "Orientation": 0,
- "VerticalTabListWidth": 256,
- "DocumentGroups": [
- {
- "DockedWidth": 200,
- "SelectedChildIndex": 4,
- "Children": [
- {
- "$type": "Document",
- "DocumentIndex": 2,
- "Title": "api.csproj",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\api\\api.csproj",
- "RelativeDocumentMoniker": "api\\api.csproj",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\api\\api.csproj",
- "RelativeToolTip": "api\\api.csproj",
- "ViewState": "AgIAAAEAAAAAAAAAAABBwBkAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
- "WhenOpened": "2026-02-04T12:40:09.071Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 3,
- "Title": "IUserService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Interfaces\\Services\\IUserService.cs",
- "RelativeDocumentMoniker": "Application\\Interfaces\\Services\\IUserService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Interfaces\\Services\\IUserService.cs",
- "RelativeToolTip": "Application\\Interfaces\\Services\\IUserService.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:38:00.905Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 4,
- "Title": "AppointmentService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\AppointmentService.cs",
- "RelativeDocumentMoniker": "Application\\Services\\AppointmentService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\AppointmentService.cs",
- "RelativeToolTip": "Application\\Services\\AppointmentService.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:37:56.288Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 5,
- "Title": "UserService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\UserService.cs",
- "RelativeDocumentMoniker": "Application\\Services\\UserService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\UserService.cs",
- "RelativeToolTip": "Application\\Services\\UserService.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:37:54.491Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 0,
- "Title": "PasswordHasherService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\PasswordHasherService.cs",
- "RelativeDocumentMoniker": "Application\\Services\\PasswordHasherService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\PasswordHasherService.cs",
- "RelativeToolTip": "Application\\Services\\PasswordHasherService.cs",
- "ViewState": "AgIAAAMAAAAAAAAAAAAqwBMAAAAcAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:37:40.758Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 7,
- "Title": "Service.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Domain\\Service.cs",
- "RelativeDocumentMoniker": "Domain\\Service.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Domain\\Service.cs",
- "RelativeToolTip": "Domain\\Service.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:37:25.567Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 6,
- "Title": "ServiceAppService.cs",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\ServiceAppService.cs",
- "RelativeDocumentMoniker": "Application\\Services\\ServiceAppService.cs",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Services\\ServiceAppService.cs",
- "RelativeToolTip": "Application\\Services\\ServiceAppService.cs",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAACEAAAAbAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-02-04T12:33:48.359Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 8,
- "Title": "Domain.csproj",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Domain\\Domain.csproj",
- "RelativeDocumentMoniker": "Domain\\Domain.csproj",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Domain\\Domain.csproj",
- "RelativeToolTip": "Domain\\Domain.csproj",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
- "WhenOpened": "2026-02-04T12:33:00.372Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 1,
- "Title": "Application.csproj",
- "DocumentMoniker": "E:\\Code\\c#\\Reservation_System\\Application\\Application.csproj",
- "RelativeDocumentMoniker": "Application\\Application.csproj",
- "ToolTip": "E:\\Code\\c#\\Reservation_System\\Application\\Application.csproj",
- "RelativeToolTip": "Application\\Application.csproj",
- "ViewState": "AgIAAAAAAAAAAAAAAAAAAA0AAAAFAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
- "WhenOpened": "2026-02-04T12:32:48.335Z",
- "EditorCaption": ""
- }
- ]
- }
- ]
- }
- ]
-}
\ No newline at end of file
diff --git a/Application/Features/Payment/DTOs/VerifyPaymentResult.cs b/Application/Features/Payment/DTOs/VerifyPaymentResult.cs
new file mode 100644
index 0000000..fd8cb43
--- /dev/null
+++ b/Application/Features/Payment/DTOs/VerifyPaymentResult.cs
@@ -0,0 +1,25 @@
+namespace Application.Features.Payment.DTOs;
+
+public class VerifyPaymentResult
+{
+ public bool IsSuccess { get; set; }
+ public string? RefNumber { get; set; }
+ public string Message { get; set; }
+
+ public static VerifyPaymentResult Success(string message)
+ {
+ return new VerifyPaymentResult
+ {
+ IsSuccess = true,
+ Message = message
+ };
+ }
+ public static VerifyPaymentResult Failed(string message)
+ {
+ return new VerifyPaymentResult
+ {
+ IsSuccess = false,
+ Message = message
+ };
+ }
+}
\ No newline at end of file
diff --git a/Application/Features/Payment/DTOs/ZarinPal/CreateZarinPalPaymentDto.cs b/Application/Features/Payment/DTOs/ZarinPal/CreateZarinPalPaymentDto.cs
index 7fbbec7..617f0fd 100644
--- a/Application/Features/Payment/DTOs/ZarinPal/CreateZarinPalPaymentDto.cs
+++ b/Application/Features/Payment/DTOs/ZarinPal/CreateZarinPalPaymentDto.cs
@@ -11,11 +11,11 @@ public class CreateZarinPalPaymentDto
[JsonPropertyName("referrer_id")]
public string? ReferrerId { get; set; }
- [JsonPropertyName("matadata")]
- public ZarinPalMataData ZarinPalMataData { get; set; }
+ [JsonPropertyName("metadata")]
+ public ZarinPalMetaData ZarinPalMetaData { get; set; }
}
-public class ZarinPalMataData
+public class ZarinPalMetaData
{
[JsonPropertyName("mobile")]
public string? Mobile { get; set; }
@@ -23,4 +23,6 @@ public class ZarinPalMataData
public string? Email { get; set; }
[JsonPropertyName("order_id")]
public string? OrderId { get; set; }
+
+ [JsonPropertyName("auto_verify")] public bool AutoVerify { get; set; }
}
\ No newline at end of file
diff --git a/Application/Features/Payment/DTOs/ZarinPal/ZarinPalVerifyResponse.cs b/Application/Features/Payment/DTOs/ZarinPal/ZarinPalVerifyResponse.cs
index acc3f4e..6b914a5 100644
--- a/Application/Features/Payment/DTOs/ZarinPal/ZarinPalVerifyResponse.cs
+++ b/Application/Features/Payment/DTOs/ZarinPal/ZarinPalVerifyResponse.cs
@@ -4,6 +4,7 @@ namespace Application.Features.Payment.DTOs.ZarinPal;
public class ZarinPalVerifyResponse
{
+ [JsonPropertyName("data")]
public ZarinPalVerifyData Data { get; set; }
}
diff --git a/Application/Features/Payment/Interfaces/PaymentGatewayProviderContract.cs b/Application/Features/Payment/Interfaces/PaymentGatewayProviderContract.cs
index 714dd7f..7b8e042 100644
--- a/Application/Features/Payment/Interfaces/PaymentGatewayProviderContract.cs
+++ b/Application/Features/Payment/Interfaces/PaymentGatewayProviderContract.cs
@@ -10,5 +10,6 @@ public interface PaymentGatewayProviderContract
Task RequestPaymentAsync(Domain.Entities.Payment payment, CreatePaymentDto dto);
- Task HandleCallBackAsync(PaymentGateway gateway, SandBoxCallBackDto dto);
+ Task HandleCallBackAsync(SandBoxCallBackDto dto);
+ Task VerifyPaymentAsync(string verifyAuthority);
}
\ No newline at end of file
diff --git a/Application/Features/Payment/Interfaces/PaymentServiceContract.cs b/Application/Features/Payment/Interfaces/PaymentServiceContract.cs
index 776b744..71de5aa 100644
--- a/Application/Features/Payment/Interfaces/PaymentServiceContract.cs
+++ b/Application/Features/Payment/Interfaces/PaymentServiceContract.cs
@@ -8,6 +8,5 @@ namespace Application.Features.Payment.Interfaces;
public interface PaymentServiceContract
{
Task CreatePaymentAsync(CreatePaymentDto dto);
- Task HandleCallBackAsync(PaymentGateway gateway,SandBoxCallBackDto dto);
- Task VerifyTransaction(string RefNum);
+ Task HandleCallBackAsync(PaymentGateway gateway,SandBoxCallBackDto dto);
}
\ No newline at end of file
diff --git a/Application/Features/Payment/Services/PaymentService.cs b/Application/Features/Payment/Services/PaymentService.cs
index 258a0c2..2ef6cd9 100644
--- a/Application/Features/Payment/Services/PaymentService.cs
+++ b/Application/Features/Payment/Services/PaymentService.cs
@@ -24,28 +24,9 @@ public PaymentService(PaymentRepositoryContract paymentRepository, IConfiguratio
_httpClient = httpClient;
_gatewayResolver = gatewayResolver;
}
-
- // public async Task CreatePaymentAsync(CreatePaymentDto dto)
- // {
- // var payment = new Domain.Entities.Payment(dto.TenantId, dto.AppointmentId, dto.Amount, dto.Gateway);
- //
- // var provider = _gatewayResolver.Resolve(dto.Gateway);
- //
- // var requestResult=await provider.RequestPaymentAsync(payment,dto);
- //
- // if (!requestResult.IsSuccess)
- // {
- // payment.MarkAsFailed();
- // await _paymentRepository.SaveAsync();
- //
- // throw new InvalidOperationException(requestResult.ErrorMessage);
- // }
- // await _paymentRepository.CreatePaymentAsync(payment);
- // await _paymentRepository.SaveAsync();
- // return requestResult.PaymentUrl;
public async Task CreatePaymentAsync(CreatePaymentDto dto)
{
- var payment = new Domain.Entities.Payment(dto.TenantId, dto.AppointmentId, dto.Amount, dto.Gateway);
+ var payment = new Domain.Entities.Payment(dto.TenantId, dto.AppointmentId, dto.Amount,dto.Description, dto.Gateway);
var provider = _gatewayResolver.Resolve(dto.Gateway);
@@ -64,30 +45,10 @@ public PaymentService(PaymentRepositoryContract paymentRepository, IConfiguratio
return requestResult.PaymentUrl;
}
- public async Task HandleCallBackAsync(PaymentGateway gateway, SandBoxCallBackDto dto)
+ public async Task HandleCallBackAsync(PaymentGateway gateway, SandBoxCallBackDto dto)
{
var provider = _gatewayResolver.Resolve(gateway);
- var result= await provider.HandleCallBackAsync(gateway, dto);
-
+ var result= await provider.HandleCallBackAsync(dto);
return result;
}
-
- public async Task VerifyTransaction(string RefNum)
- {
- var request = new
- {
- refNum = RefNum,
- TerminalNumber = _configuration["Payment:TerminalId"]
- };
- var verifyUrl = _configuration["Payment:VerifyTransactionUrl"];
- var response = await _httpClient.PostAsJsonAsync(verifyUrl, request);
-
- if (!response.IsSuccessStatusCode)
- return false;
-
- var result = await response.Content.ReadAsStringAsync();
- Console.WriteLine(result);
- return result is not null;
- }
-
}
\ No newline at end of file
diff --git a/Domain/Entities/Payment.cs b/Domain/Entities/Payment.cs
index b887ac5..88b4592 100644
--- a/Domain/Entities/Payment.cs
+++ b/Domain/Entities/Payment.cs
@@ -32,7 +32,7 @@ public class Payment
public DateTime? PaidAt { get; set; }
- public Payment(Guid tenantId, Guid appointmentId, long amount, PaymentGateway gateway)
+ public Payment(Guid tenantId, Guid appointmentId, long amount,string description, PaymentGateway gateway)
{
Id = Guid.NewGuid();
TenantId = tenantId;
@@ -40,6 +40,7 @@ public Payment(Guid tenantId, Guid appointmentId, long amount, PaymentGateway ga
Amount = amount;
Gateway=gateway;
PaymentStatus = PaymentStatus.pending;
+ Description=description;
CreatedAt = DateTime.UtcNow;
}
@@ -70,7 +71,11 @@ public void MarkAsFailed()
}
public void MarkAsSuccess()
{
+ if (PaymentStatus == PaymentStatus.Success)
+ return;
+
PaymentStatus = PaymentStatus.Success;
+ PaidAt??=DateTime.UtcNow;
}
public void GenerateOrderNumber()
diff --git a/Infrastructure/Services/Implement/SamanPaymentGatewayProvider.cs b/Infrastructure/Services/Implement/SamanPaymentGatewayProvider.cs
index 491f565..9c586da 100644
--- a/Infrastructure/Services/Implement/SamanPaymentGatewayProvider.cs
+++ b/Infrastructure/Services/Implement/SamanPaymentGatewayProvider.cs
@@ -66,16 +66,15 @@ public async Task RequestPaymentAsync(Payment payme
}
var paymentUrl = $"{paymentPageUrl}?token={result.Token}";
-
return PaymentGatewayRequestResult.Success(result.Token, paymentUrl);
}
- public async Task HandleCallBackAsync(PaymentGateway gateway, SandBoxCallBackDto dto)
+ public async Task HandleCallBackAsync(SandBoxCallBackDto dto)
{
- var payment=await _paymentRepositoryContract.GetPaymentByAuthorityAsync(dto.Authority);
+ var payment=await _paymentRepositoryContract.GetPaymentByResNumAsync(dto.ResNum);
var request = new
{
- RefNum = payment.RefNum,
+ RefNum = dto.RefNum,
TerminalNumber = _configuration["Payment:TerminalId"]
};
var verifyUrl = _configuration["Payment:VerifyTransactionUrl"];
@@ -85,9 +84,13 @@ public async Task RequestPaymentAsync(Payment payme
var result=await response.Content.ReadFromJsonAsync();
if (!response.IsSuccessStatusCode)
- return "پرداخت ناموفق";
+ return VerifyPaymentResult.Failed("تراکنش ناموفق بود");
- return result.ResultDescription;
+ return VerifyPaymentResult.Success(result.ResultDescription);
}
+ public Task VerifyPaymentAsync(string verifyAuthority)
+ {
+ throw new NotImplementedException();
+ }
}
\ No newline at end of file
diff --git a/Infrastructure/Services/Implement/ZarinPalPaymentGatewayProvider.cs b/Infrastructure/Services/Implement/ZarinPalPaymentGatewayProvider.cs
index 35ff3dc..2722056 100644
--- a/Infrastructure/Services/Implement/ZarinPalPaymentGatewayProvider.cs
+++ b/Infrastructure/Services/Implement/ZarinPalPaymentGatewayProvider.cs
@@ -15,7 +15,8 @@ public class ZarinPalPaymentGatewayProvider : PaymentGatewayProviderContract
private readonly HttpClient _httpClient;
private readonly PaymentRepositoryContract _repositoryContract;
- public ZarinPalPaymentGatewayProvider(IConfiguration config, HttpClient httpClient, PaymentRepositoryContract repositoryContract)
+ public ZarinPalPaymentGatewayProvider(IConfiguration config, HttpClient httpClient,
+ PaymentRepositoryContract repositoryContract)
{
_config = config;
_httpClient = httpClient;
@@ -32,11 +33,12 @@ public async Task RequestPaymentAsync(Payment payme
Amount = dto.Amount,
Description = dto.Description,
ReferrerId = dto.ReferrerId,
- ZarinPalMataData = new ZarinPalMataData
+ ZarinPalMetaData = new ZarinPalMetaData
{
Mobile = dto.Mobile,
Email = dto.Email,
- OrderId = payment.ResNum
+ OrderId = payment.ResNum,
+ AutoVerify = bool.Parse(_config["Payment:ZarinPal_AutoVerify"])
}
};
var request = new
@@ -45,7 +47,7 @@ public async Task RequestPaymentAsync(Payment payme
amount = requestDto.Amount,
description = requestDto.Description,
callback_url = _config["Payment:ZarinPalCallBackUrl"],
- matadata = requestDto.ZarinPalMataData
+ metadata = requestDto.ZarinPalMetaData
};
using var response =
@@ -61,7 +63,7 @@ public async Task RequestPaymentAsync(Payment payme
}
var result = JsonSerializer.Deserialize(raw);
- if (result is null || string.IsNullOrWhiteSpace(result.Data.Authority))
+ if (string.IsNullOrWhiteSpace(result?.Data?.Authority))
{
return PaymentGatewayRequestResult.Failed(
"INVALID_RESPONSE",
@@ -72,28 +74,95 @@ public async Task RequestPaymentAsync(Payment payme
return PaymentGatewayRequestResult.Success(result.Data.Authority, paymentUrl);
}
- public async Task HandleCallBackAsync(PaymentGateway gateway, SandBoxCallBackDto dto)
+ public async Task HandleCallBackAsync(SandBoxCallBackDto dto)
{
+ var payment = await _repositoryContract.GetPaymentByAuthorityAsync(dto.Authority);
+
+ if (payment is null)
+ return VerifyPaymentResult.Failed("تراکنش یافت نشد.");
+
if (dto.Status != "OK")
{
- return "پرداخت ناموفق";
+ payment.Edit("NOK", null, null, 0);
+ payment.MarkAsFailed();
+ await _repositoryContract.SaveAsync();
+
+ return VerifyPaymentResult.Failed("پرداخت توسط کاربر لغو شد.");
+ }
+
+ if (payment.PaymentStatus == PaymentStatus.Success)
+ {
+ return VerifyPaymentResult.Success(
+ $"این تراکنش قبلاً تایید شده است. شماره پیگیری: {payment.RefNum}");
}
- var payment=await _repositoryContract.GetPaymentByAuthorityAsync(dto.Authority);
+
+ return await VerifyPaymentAsync(dto.Authority);
+ }
+
+
+
+ public async Task VerifyPaymentAsync(string authority)
+ {
+ var payment = await _repositoryContract.GetPaymentByAuthorityAsync(authority);
+
+ if (payment is null)
+ return VerifyPaymentResult.Failed("تراکنش یافت نشد");
+
var verifyRequest = new
{
merchant_id = _config["Payment:MerchantIdZarinPal"],
amount = payment.Amount,
- authority = dto.Authority
+ authority = authority
};
- var verifyResponse =
- await _httpClient.PostAsJsonAsync("https://sandbox.zarinpal.com/pg/v4/payment/verify.json", verifyRequest);
- var result = await verifyResponse.Content.ReadFromJsonAsync();
+ try
+ {
+ var response = await _httpClient.PostAsJsonAsync(
+ _config["Payment:ZarinPalVerifyUrl"],
+ verifyRequest);
+
+ var raw = await response.Content.ReadAsStringAsync();
- if (result.Data.Code != 100 && result.Data.Code != 101)
- return "پرداخت ناموفق .";
- payment.Edit(dto.Status, result.Data.RefId, result.Data.CardPan, result.Data.Fee);
- await _repositoryContract.SaveAsync();
- return "پرداخت با موفقیت انجام شد شماره پیگیری " + result.Data.RefId;
+ if (!response.IsSuccessStatusCode)
+ return VerifyPaymentResult.Failed($"خطا از درگاه: {raw}");
+
+ var result = JsonSerializer.Deserialize(raw);
+
+ if (result?.Data is null)
+ return VerifyPaymentResult.Failed("پاسخ نامعتبر از درگاه زرین پال");
+
+ if (result.Data.Code == 100)
+ {
+ payment.Edit("OK", result.Data.RefId, result.Data.CardPan, result.Data.Fee);
+ payment.MarkAsSuccess();
+ await _repositoryContract.SaveAsync();
+
+ return VerifyPaymentResult.Success(
+ $"تراکنش با شماره پیگیری {result.Data.RefId} انجام شد");
+ }
+
+ if (result.Data.Code == 101)
+ {
+ if (payment.PaymentStatus != PaymentStatus.Success)
+ {
+ payment.Edit("OK", result.Data.RefId, result.Data.CardPan, result.Data.Fee);
+ payment.MarkAsSuccess();
+ await _repositoryContract.SaveAsync();
+ }
+
+ return VerifyPaymentResult.Success(
+ $"این تراکنش قبلاً تایید شده است. شماره پیگیری: {result.Data.RefId}");
+ }
+
+ payment.Edit("NOK", null, null, 0);
+ payment.MarkAsFailed();
+ await _repositoryContract.SaveAsync();
+
+ return VerifyPaymentResult.Failed("تایید تراکنش توسط درگاه انجام نشد.");
+ }
+ catch (Exception)
+ {
+ return VerifyPaymentResult.Failed("خطای غیر منتظره در ارتباط با درگاه.");
+ }
}
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 3007258..5cf593c 100644
--- a/README.md
+++ b/README.md
@@ -1,206 +1,343 @@
+# Booking System API
-# 📅 Appointment Reservation System API
+یک API برای **سیستم رزرو نوبت چندمستاجری (Multi-Tenant)** که با **ASP.NET Core 8** پیادهسازی شده و قابلیتهایی مثل احراز هویت JWT، مدیریت کاربران و نقشها، رزرو نوبت، مدیریت سرویسها و اتصال به درگاه پرداخت را پوشش میدهد.
-## 🎯 معرفی پروژه
-این پروژه یک **API سیستم رزرو نوبت** است که با **ASP.NET Core** و بر اساس اصول **Clean Architecture** طراحی و پیادهسازی شده است.
+> این پروژه برای نمایش معماری، طراحی API و پیادهسازی یک سیستم رزرو قابل توسعه آماده شده است.
-تمرکز اصلی پروژه بر:
-- ✅ امنیت مبتنی بر JWT
-- ✅ طراحی RESTful واقعی
-- ✅ جداسازی مسئولیتها
-- ✅ جلوگیری از ارسال و دستکاری شناسه کاربر
-- ✅ اعمال Authorization در لایه Application
+---
+
+## فهرست مطالب
-میباشد؛ بهطوری که API برای استفاده در پروژههای واقعی و Production-ready مناسب است.
+- [ویژگیها](#ویژگیها)
+- [تکنولوژیها](#تکنولوژیها)
+- [معماری پروژه](#معماری-پروژه)
+- [ماژولها](#ماژولها)
+- [احراز هویت و سطح دسترسی](#احراز-هویت-و-سطح-دسترسی)
+- [Multi-Tenancy](#multi-tenancy)
+- [درگاه پرداخت](#درگاه-پرداخت)
+- [راهاندازی پروژه](#راهاندازی-پروژه)
+- [Endpointهای اصلی](#endpointهای-اصلی)
+- [نکات امنیتی برای انتشار عمومی](#نکات-امنیتی-برای-انتشار-عمومی)
+- [وضعیت پروژه](#وضعیت-پروژه)
---
-## 🧱 معماری پروژه (Clean Architecture)
+## ویژگیها
+
+- پیادهسازی API رزرو نوبت با ASP.NET Core
+- معماری چندلایه بر اساس اصول Clean Architecture
+- پشتیبانی از Multi-Tenancy برای تفکیک دادهها بر اساس Tenant
+- احراز هویت با JWT
+- پشتیبانی از Refresh Token
+- مدیریت نقشها شامل `SuperAdmin`، `Admin`، `User` و `Customer`
+- مدیریت کاربران، پروفایل و خروج از حساب کاربری
+- مدیریت سرویسهای قابل رزرو
+- ایجاد، ویرایش، مشاهده و لغو نوبت
+- اتصال به درگاه پرداخت با پشتیبانی از Saman و ZarinPal
+- اعتبارسنجی ورودیها با FluentValidation
+- مستندسازی API با Swagger
+- استفاده از Entity Framework Core و SQL Server
-ساختار کلی پروژه:
+---
+## تکنولوژیها
-```
-Api
- └── Controllers
- └── REST Endpoints
+- .NET 8
+- ASP.NET Core Web API
+- Entity Framework Core
+- SQL Server
+- JWT Bearer Authentication
+- Refresh Token
+- FluentValidation
+- Swagger / Swashbuckle
-Application
- ├── Interfaces
- │ └── IUserContext
- ├── Features
- │ ├── Appointment
- │ ├── User
- │ └── Service
- ├── DTOs
- └── Application Services
+---
-Infrastructure
- ├── Security
- │ ├── JWT
- │ └── UserContext
- └── Persistence
+## معماری پروژه
+
+پروژه به صورت چندلایه طراحی شده است:
+
+```text
+Booking-System
+├── api
+│ ├── Controllers
+│ ├── Middlewares
+│ └── Program.cs
+├── Application
+│ ├── Common
+│ ├── Features
+│ └── Validators
+├── Domain
+│ ├── Entities
+│ ├── Enums
+│ ├── Exceptions
+│ └── Interfaces
+└── Infrastructure
+ ├── Persistence
+ ├── Repositories
+ ├── Security
+ └── Services
```
-✅ لایه `Application` مستقل از `Api` و `Infrastructure` است
-✅ هیچ منطق امنیتی حیاتی صرفاً در Controllerها قرار ندارد
+### توضیح لایهها
+
+- `Domain`: شامل Entityها، Enumها، Exceptionها و قراردادهای اصلی دامنه
+- `Application`: شامل Use Caseها، سرویسهای اپلیکیشن، DTOها، Validatorها و Interfaceها
+- `Infrastructure`: شامل EF Core، Repositoryها، JWT، Hashing و پیادهسازی سرویسهای خارجی
+- `api`: شامل Controllerها، Middlewareها، Swagger و پیکربندی اجرای برنامه
---
-## 🔐 احراز هویت (JWT Authentication)
+## ماژولها
+
+### User / Auth
+
+- ثبتنام کاربر
+- ورود با ایمیل و رمز عبور
+- انتخاب Tenant هنگام وجود چند هویت برای یک ایمیل
+- صدور Access Token و Refresh Token
+- تمدید Token
+- خروج از حساب کاربری
+- مشاهده و ویرایش پروفایل
+- مشاهده کاربران توسط `SuperAdmin`
+
+### Tenant
+
+- ثبت Tenant جدید
+- اتصال کاربر به Tenant
+- بررسی وضعیت اشتراک Tenant
+- نگهداری `TenantId` در Context برنامه
+
+### Service
+
+- ایجاد سرویس قابل رزرو
+- ویرایش سرویس
+- حذف سرویس
+- مشاهده سرویسها
+
+### Appointment
+
+- ایجاد نوبت
+- اتصال نوبت به یک یا چند سرویس
+- ویرایش سرویسهای نوبت
+- لغو نوبت
+- مشاهده نوبتها
+- بررسی مالکیت نوبت برای جلوگیری از دسترسی غیرمجاز
-### JWT
-- احراز هویت کاربران با **JSON Web Token**
-- ارسال Token در Header درخواست:
+### Payment
+
+- ایجاد پرداخت برای نوبت
+- انتخاب درگاه پرداخت
+- دریافت URL پرداخت
+- مدیریت Callback درگاه
+- ثبت وضعیت پرداخت
+- ذخیره اطلاعاتی مثل `Authority`، `RefNum`، `RRN`، `TraceNo` و `SecurePan`
+
+---
+
+## احراز هویت و سطح دسترسی
+
+احراز هویت با JWT انجام میشود. برای دسترسی به endpointهای محافظتشده باید Token در Header ارسال شود:
+
+```http
+Authorization: Bearer
```
-Authorization: Bearer {token}
+
+### نقشها
+
+```text
+SuperAdmin
+Admin
+User
+Customer
```
-### UserContext
-برای حذف وابستگی به `userId` ارسالی از کلاینت:
+برخی عملیات مثل مدیریت سرویسها، مشاهده همه کاربران، مشاهده همه نوبتها و ثبت Tenant فقط برای نقشهای مدیریتی در نظر گرفته شدهاند.
-- Interface: `IUserContext` در لایه Application
-- Implementation: `UserContext` در Infrastructure
-- استخراج اطلاعات از JWT Claims:
- - `UserId` ← `sub` یا `ClaimTypes.NameIdentifier`
- - `Role` ← `ClaimTypes.Role`
+---
-✅ تمام عملیات وابسته به کاربر، هویت را از JWT میگیرند، نه از ورودی کلاینت.
+## Multi-Tenancy
+
+این پروژه با رویکرد Multi-Tenant طراحی شده است. موجودیتهایی مثل کاربر، سرویس، نوبت، Refresh Token و پرداخت به Tenant متصل هستند و دادهها بر اساس `TenantId` تفکیک میشوند.
+
+در حال حاضر Tenant از طریق Context و Claimهای JWT مدیریت میشود و Middleware اختصاصی برای بررسی وضعیت Tenant و اعتبار اشتراک در پروژه وجود دارد.
---
-## 🛡️ Authorization و Role Management
+## درگاه پرداخت
-### EnsureAdmin
-برای عملیات Admin-only، متد زیر در AppServiceها پیادهسازی شده است:
+پروژه ساختار قابل توسعه برای اتصال به چند درگاه پرداخت دارد.
-```csharp
-EnsureAdmin();
-```
+درگاههای فعلی:
+
+- Saman
+- ZarinPal
-این متد:
-- Role کاربر جاری را بررسی میکند
-- در صورت عدم دسترسی، Exception پرتاب میکند
+نمونه سناریوی پرداخت:
-✅ حتی اگر Controller اشتباه پیکربندی شود،
-لایه Application همچنان امنیت را حفظ میکند (**Defense in Depth**).
+1. کاربر برای یک نوبت درخواست پرداخت ایجاد میکند.
+2. سیستم بر اساس درگاه انتخابشده URL پرداخت تولید میکند.
+3. کاربر به صفحه پرداخت منتقل میشود.
+4. درگاه، نتیجه پرداخت را به Callback API ارسال میکند.
+5. وضعیت پرداخت در سیستم ثبت و بروزرسانی میشود.
---
-## 👤 بررسی مالکیت (Ownership Check)
+## راهاندازی پروژه
-برای عملیات حساس کاربرمحور مانند:
-- ویرایش نوبت
-- لغو نوبت
-- مشاهده نوبتهای شخصی
+### پیشنیازها
+
+- .NET SDK 8
+- SQL Server
+- IDE یا Editor مثل Visual Studio، Rider یا VS Code
+
+### مراحل اجرا
+
+1. ریپازیتوری را Clone کنید:
-مالکیت بررسی میشود:
+```bash
+git clone https://github.com//.git
+cd
+```
+
+2. پکیجها را Restore کنید:
+```bash
+dotnet restore
```
-appointment.UserId == CurrentUserId
+
+3. فایل تنظیمات نمونه را کپی کنید و مقدار Connection String، JWT و درگاه پرداخت را تنظیم کنید:
+
+```bash
+cp api/appsettings.example.json api/appsettings.json
```
-✅ هیچ کاربری به منابع کاربران دیگر دسترسی ندارد.
+برای پروژههای واقعی بهتر است مقادیر حساس را در Secret Manager یا Environment Variable نگهداری کنید:
+
+```json
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "Server=.;Database=BookingDb;Trusted_Connection=True;TrustServerCertificate=True"
+ },
+ "JwtSettings": {
+ "Issuer": "https://localhost:44328",
+ "Audience": "https://localhost:44328",
+ "SecretKey": "CHANGE_ME_TO_A_LONG_RANDOM_SECRET",
+ "AccessTokenMinutes": 10
+ }
+}
+```
----
+4. دیتابیس را ایجاد یا بروزرسانی کنید:
-## 📅 Appointment API
+```bash
+dotnet ef database update --project Infrastructure --startup-project api
+```
-### قابلیتها
-- ایجاد نوبت
-- ویرایش جزئی نوبت (PATCH)
-- لغو نوبت
-- مشاهده نوبتهای شخصی (JWT-Based)
-- مشاهده تمام نوبتها (Admin)
+5. پروژه API را اجرا کنید:
-### Endpointها
+```bash
+dotnet run --project api
```
-POST /api/appointment
-PATCH /api/appointment/{id}
-DELETE /api/appointment/{id}
-GET /api/appointment/my
-GET /api/appointment (Admin only)
+
+6. Swagger را در محیط Development باز کنید:
+
+```text
+https://localhost:/swagger
```
---
-## 👥 User API
+## Endpointهای اصلی
-### امکانات
-- ثبتنام و ورود
-- دریافت پروفایل کاربر جاری
-- ویرایش پروفایل
-- تغییر رمز عبور
-- حذف حساب کاربری
-- مشاهده لیست کاربران (Admin)
+### User
-### Endpointها
+```http
+POST /api/User/Register
+POST /api/User/login
+POST /api/User/refresh
+POST /api/User/Logout
+GET /api/User/Profile
+PATCH /api/User/Profile
+GET /api/User/ViewUsers
```
-POST /api/user/register
-POST /api/user/login
-POST /api/user/refresh
-GET /api/user/me
-PATCH /api/user/me
-POST /api/user/change-password
-DELETE /api/user/me
-GET /api/user (Admin only)
+
+### Auth
+
+```http
+POST /api/Auth/Select-Tenant
```
----
+### Tenant
-## 🌐 اصول REST رعایتشده
-- ✅ استفاده صحیح از HTTP Verbs
-- ✅ Resource-based URL
-- ✅ حذف Action-based Routeها
-- ✅ استفاده از `me` بهجای ارسال userId
-- ✅ عدم دریافت اطلاعات هویتی حساس از کلاینت
+```http
+POST /api/Tenant
+GET /api/Tenant
+```
----
+### Service
-## 🧪 متدهای HTTP
+```http
+POST /api/Service
+PATCH /api/Service/{serviceId}
+DELETE /api/Service/{serviceId}
+GET /api/Service/ViewServices
+```
-| Method | کاربرد |
-|------|------|
-| GET | دریافت اطلاعات |
-| POST | ایجاد Resource |
-| PATCH | ویرایش جزئی |
-| DELETE | حذف / لغو |
+### Appointment
----
+```http
+POST /api/Appointment
+PATCH /api/Appointment/{appointmentId}
+DELETE /api/Appointment/{appointmentId}
+GET /api/Appointment/ViewAppointments
+GET /api/Appointment/ViewMyAppointments
+```
-## ⚙️ تکنولوژیها
-- ASP.NET Core
-- JWT Authentication
-- Clean Architecture
-- Entity Framework Core
-- FluentValidation
+### Payment
+
+```http
+POST /api/Payment/GetPaymentUrl
+GET /api/Payment/CallBack/{gateway}
+POST /api/Payment/CallBack/{gateway}
+```
---
-## 🚀 اجرای پروژه
+## نکات امنیتی برای انتشار عمومی
+
+قبل از Public کردن ریپازیتوری در GitHub:
+
+- مقدار واقعی `JwtSettings:SecretKey` را از فایلهای تنظیمات حذف کنید.
+- اطلاعات واقعی Super Admin، شماره موبایل، ایمیل و رمز عبور را در ریپازیتوری قرار ندهید.
+- Merchant ID، Terminal ID، Callback URL و کلیدهای درگاه پرداخت را در Secret Manager، Environment Variable یا GitHub Secrets نگهداری کنید.
+- اگر Secret واقعی قبلاً Commit شده، آن را Rotate کنید.
+- فایلهای `bin`، `obj`، لاگها و فایلهای local development را Commit نکنید.
+- برای Production مقدار `RequireHttpsMetadata`، تنظیمات CORS، مدیریت خطا و Logging را بازبینی کنید.
+
+نمونه استفاده از Secret Manager:
+
```bash
-dotnet restore
-dotnet run
+dotnet user-secrets init --project api
+dotnet user-secrets set "JwtSettings:SecretKey" "YOUR_LONG_RANDOM_SECRET" --project api
+dotnet user-secrets set "ConnectionStrings:DefaultConnection" "YOUR_CONNECTION_STRING" --project api
```
---
-## ⚠️ وضعیت توسعه و نکات مهم
-این پروژه در حال حاضر **نسخهی اولیه (v1)** محسوب میشود.
+## وضعیت پروژه
-با وجود اینکه:
-- ساختار معماری بهصورت اصولی (Clean Architecture) طراحی شده
-- ملاحظات امنیتی (JWT، Authorization، Ownership Check) رعایت شده
-- API از نظر RESTful و Business Logic پایدار است
+این پروژه در حال توسعه است و ممکن است در آینده موارد زیر تغییر کند:
-ممکن است:
-- 🐞 باگهای منطقی یا لبهای (Edge Case) وجود داشته باشد
-- 🔄 برخی بخشها در آینده Refactor یا بهینهسازی شوند
-- ➕ قابلیتهای جدید یا تغییر در Endpointها اضافه شود
+- بهبود ساختار Multi-Tenancy
+- تکمیل Password Recovery
+- بهبود Error Handling و Exception Middleware
+- اضافه شدن تستهای واحد و یکپارچه
+- بهبود مستندات Swagger
+- بازبینی کامل تنظیمات Production
-✅ پروژه بهصورت **Active Development** نگهداری میشود و
-در بازههای زمانی مختلف بهروزرسانی خواهد شد.
+---
-> هدف این نسخه، ارائهی یک پایهی تمیز، امن و قابل توسعه برای ادامهی مسیر است.
-
+## مشارکت
+Pull Requestها و پیشنهادها خوشحالکنندهاند. قبل از ارسال تغییرات، لطفاً پروژه را Build کنید و مطمئن شوید تنظیمات حساس در Commitها قرار نگرفتهاند.
diff --git a/Reservation_System.sln.DotSettings.user b/Reservation_System.sln.DotSettings.user
deleted file mode 100644
index 9a85fc2..0000000
--- a/Reservation_System.sln.DotSettings.user
+++ /dev/null
@@ -1,5 +0,0 @@
-
- ForceIncluded
- ForceIncluded
- ForceIncluded
-
\ No newline at end of file
diff --git a/api/api.csproj.user b/api/api.csproj.user
deleted file mode 100644
index 9ff5820..0000000
--- a/api/api.csproj.user
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
- https
-
-
\ No newline at end of file
diff --git a/api/appsettings.example.json b/api/appsettings.example.json
new file mode 100644
index 0000000..43d9311
--- /dev/null
+++ b/api/appsettings.example.json
@@ -0,0 +1,34 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "ConnectionStrings": {
+ "DefaultConnection": "Server=.;Database=BookingDb;Trusted_Connection=True;TrustServerCertificate=True"
+ },
+ "JwtSettings": {
+ "Issuer": "https://localhost:44328",
+ "Audience": "https://localhost:44328",
+ "SecretKey": "CHANGE_ME_TO_A_LONG_RANDOM_SECRET_KEY",
+ "AccessTokenMinutes": 10
+ },
+ "SeedSuperAdmin": {
+ "FullName": "Super Admin",
+ "Email": "admin@example.com",
+ "PhoneNumber": "09120000000",
+ "Password": "CHANGE_ME"
+ },
+ "Payment": {
+ "TerminalId": "CHANGE_ME",
+ "SamanRedirectUrl": "https://localhost:44328/api/Payment/CallBack/0",
+ "VerifyTransactionUrl": "https://sandbox.banktest.ir/saman/sep.shaparak.ir/verifyTxnRandomSessionkey/ipg/VerifyTransaction",
+ "MerchantIdZarinPal": "CHANGE_ME",
+ "ZarinPalCallBackUrl": "https://localhost:44328/api/Payment/CallBack/1",
+ "ZarinPalVerifyUrl": "https://sandbox.zarinpal.com/pg/v4/payment/verify.json",
+ "ZarinPal_AutoVerify": false,
+ "ZarinPalCurrency": "IRT"
+ }
+}
diff --git a/appsettings.Development.json b/appsettings.Development.json
deleted file mode 100644
index 0c208ae..0000000
--- a/appsettings.Development.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- }
-}
diff --git a/appsettings.json b/appsettings.json
deleted file mode 100644
index 10f68b8..0000000
--- a/appsettings.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "AllowedHosts": "*"
-}