├── .dockerignore
├── .gitignore
├── Dockerfile
├── LICENSE
├── NPay.rest
├── NPay.sln
├── README.md
├── docker-compose.yml
└── src
├── Bootstrapper
└── NPay.Bootstrapper
│ ├── NPay.Bootstrapper.csproj
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── Startup.cs
│ ├── appsettings.development.json
│ ├── appsettings.docker.json
│ └── appsettings.json
├── Modules
├── Notifications
│ ├── NPay.Modules.Notifications.Api
│ │ ├── Extensions.cs
│ │ ├── Handlers
│ │ │ ├── Owners
│ │ │ │ └── OwnerVerifiedHandler.cs
│ │ │ ├── Users
│ │ │ │ ├── UserCreatedHandler.cs
│ │ │ │ └── UserVerifiedHandler.cs
│ │ │ └── Wallets
│ │ │ │ ├── FundsAddedHandler.cs
│ │ │ │ ├── FundsTransferredHandler.cs
│ │ │ │ └── WalletAddedHandler.cs
│ │ ├── NPay.Modules.Notifications.Api.csproj
│ │ └── Services
│ │ │ ├── EmailResolver.cs
│ │ │ ├── EmailSender.cs
│ │ │ ├── IEmailResolver.cs
│ │ │ ├── IEmailSender.cs
│ │ │ └── NotificationsModuleApi.cs
│ └── NPay.Modules.Notifications.Shared
│ │ ├── INotificationsModuleApi.cs
│ │ └── NPay.Modules.Notifications.Shared.csproj
├── Users
│ ├── NPay.Modules.Users.Api
│ │ ├── Controllers
│ │ │ └── UsersController.cs
│ │ ├── Extensions.cs
│ │ └── NPay.Modules.Users.Api.csproj
│ ├── NPay.Modules.Users.Core
│ │ ├── DAL
│ │ │ ├── Configurations
│ │ │ │ └── UserConfiguration.cs
│ │ │ ├── Migrations
│ │ │ │ ├── 20211019160150_Users_Init.Designer.cs
│ │ │ │ ├── 20211019160150_Users_Init.cs
│ │ │ │ └── UsersDbContextModelSnapshot.cs
│ │ │ └── UsersDbContext.cs
│ │ ├── Entities
│ │ │ └── User.cs
│ │ ├── Exceptions
│ │ │ ├── InvalidAddressException.cs
│ │ │ ├── InvalidEmailException.cs
│ │ │ ├── InvalidFullNameException.cs
│ │ │ ├── UserAlreadyExistsException.cs
│ │ │ ├── UserAlreadyVerifiedException.cs
│ │ │ └── UserNotFoundException.cs
│ │ ├── Extensions.cs
│ │ ├── NPay.Modules.Users.Core.csproj
│ │ └── Services
│ │ │ ├── IUsersService.cs
│ │ │ ├── UsersModuleApi.cs
│ │ │ └── UsersService.cs
│ ├── NPay.Modules.Users.Shared
│ │ ├── DTO
│ │ │ ├── UserDetailsDto.cs
│ │ │ └── UserDto.cs
│ │ ├── Events
│ │ │ ├── UserCreated.cs
│ │ │ └── UserVerified.cs
│ │ ├── IUsersModuleApi.cs
│ │ └── NPay.Modules.Users.Shared.csproj
│ └── Users.rest
└── Wallets
│ ├── NPay.Modules.Wallets.Api
│ ├── Controllers
│ │ ├── FundsController.cs
│ │ ├── OwnersController.cs
│ │ └── WalletsController.cs
│ ├── Extensions.cs
│ └── NPay.Modules.Wallets.Api.csproj
│ ├── NPay.Modules.Wallets.Application
│ ├── Extensions.cs
│ ├── NPay.Modules.Wallets.Application.csproj
│ ├── Owners
│ │ ├── Commands
│ │ │ ├── AddOwner.cs
│ │ │ └── Handlers
│ │ │ │ └── AddOwnerHandler.cs
│ │ ├── Exceptions
│ │ │ ├── UserAlreadyExistsException.cs
│ │ │ └── UserNotFoundException.cs
│ │ └── ExternalEvents
│ │ │ └── Handlers
│ │ │ ├── UserCreatedHandler.cs
│ │ │ └── UserVerifiedHandler.cs
│ ├── Services
│ │ └── WalletsModuleApi.cs
│ └── Wallets
│ │ ├── Commands
│ │ ├── AddFunds.cs
│ │ ├── AddWallet.cs
│ │ ├── Handlers
│ │ │ ├── AddFundsHandler.cs
│ │ │ ├── AddWalletHandler.cs
│ │ │ └── TransferFundsHandler.cs
│ │ └── TransferFunds.cs
│ │ ├── ExternalEvents
│ │ └── Handlers
│ │ │ └── UserVerifiedHandler.cs
│ │ ├── Queries
│ │ ├── GetWallet.cs
│ │ └── Handlers
│ │ │ ├── Extensions.cs
│ │ │ └── GetWalletHandler.cs
│ │ └── Storage
│ │ ├── Extensions.cs
│ │ └── IWalletStorage.cs
│ ├── NPay.Modules.Wallets.Core
│ ├── Extensions.cs
│ ├── NPay.Modules.Wallets.Core.csproj
│ ├── Owners
│ │ ├── Aggregates
│ │ │ └── Owner.cs
│ │ ├── Exceptions
│ │ │ ├── InvalidFullNameException.cs
│ │ │ ├── InvalidNationalityException.cs
│ │ │ ├── OwnerNotFoundException.cs
│ │ │ └── UnsupportedNationalityException.cs
│ │ ├── Repositories
│ │ │ └── IOwnerRepository.cs
│ │ └── ValueObjects
│ │ │ ├── FullName.cs
│ │ │ └── Nationality.cs
│ ├── SharedKernel
│ │ ├── AggregateRoot.cs
│ │ └── OwnerId.cs
│ └── Wallets
│ │ ├── Aggregates
│ │ └── Wallet.cs
│ │ ├── Entities
│ │ └── Transfer.cs
│ │ ├── Exceptions
│ │ ├── InsufficientWalletFundsException.cs
│ │ ├── InvalidAmountException.cs
│ │ ├── InvalidCurrencyException.cs
│ │ ├── InvalidTransferAmountException.cs
│ │ ├── UnsupportedCurrencyException.cs
│ │ └── WalletNotFoundException.cs
│ │ ├── Repositories
│ │ └── IWalletRepository.cs
│ │ ├── Services
│ │ ├── CurrencyResolver.cs
│ │ └── ICurrencyResolver.cs
│ │ └── ValueObjects
│ │ ├── Amount.cs
│ │ ├── Currency.cs
│ │ ├── TransferId.cs
│ │ └── WalletId.cs
│ ├── NPay.Modules.Wallets.Infrastructure
│ ├── DAL
│ │ ├── Configurations
│ │ │ ├── OwnerConfiguration.cs
│ │ │ ├── TransferConfiguration.cs
│ │ │ └── WalletConfiguration.cs
│ │ ├── Migrations
│ │ │ ├── 20211020191405_Wallets_Init.Designer.cs
│ │ │ ├── 20211020191405_Wallets_Init.cs
│ │ │ └── WalletsDbContextModelSnapshot.cs
│ │ ├── Repositories
│ │ │ ├── OwnerRepository.cs
│ │ │ └── WalletRepository.cs
│ │ └── WalletsDbContext.cs
│ ├── Extensions.cs
│ ├── NPay.Modules.Wallets.Infrastructure.csproj
│ └── Storage
│ │ └── WalletStorage.cs
│ ├── NPay.Modules.Wallets.Shared
│ ├── DTO
│ │ ├── TransferDto.cs
│ │ └── WalletDto.cs
│ ├── Events
│ │ ├── FundsAdded.cs
│ │ ├── FundsTransfered.cs
│ │ ├── OwnerVerified.cs
│ │ └── WalletAdded.cs
│ ├── IWalletsModuleApi.cs
│ └── NPay.Modules.Wallets.Shared.csproj
│ └── Wallets.rest
└── Shared
└── NPay.Shared
├── Commands
├── CommandDispatcher.cs
├── Extensions.cs
├── ICommand.cs
├── ICommandDispatcher.cs
└── ICommandHandler.cs
├── Database
├── DbContextAppInitializer.cs
├── Extensions.cs
└── PostgresOptions.cs
├── Dispatchers
├── IDispatcher.cs
└── InMemoryDispatcher.cs
├── Events
├── EventDispatcher.cs
├── Extensions.cs
├── IEvent.cs
├── IEventDispatcher.cs
└── IEventHandler.cs
├── Exceptions
├── ErrorHandlerMiddleware.cs
├── ExceptionResponse.cs
├── ExceptionToResponseMapper.cs
├── Extensions.cs
├── IExceptionToResponseMapper.cs
└── NPayException.cs
├── Extensions.cs
├── Messaging
├── AsyncEventDispatcher.cs
├── EventChannel.cs
├── EventDispatcherJob.cs
├── Extensions.cs
├── IAsyncEventDispatcher.cs
├── IEventChannel.cs
├── IMessageBroker.cs
└── InMemoryMessageBroker.cs
├── NPay.Shared.csproj
├── Queries
├── Extensions.cs
├── IQuery.cs
├── IQueryDispatcher.cs
├── IQueryHandler.cs
└── QueryDispatcher.cs
└── Time
├── IClock.cs
└── UtcClock.cs
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/[Oo]bj
2 | **/[Bb]in
3 | **/[Ll]ogs
4 | TestResults/
5 | .nuget/
6 | _ReSharper.*/
7 | packages/
8 | artifacts/
9 | PublishProfiles/
10 | *.user
11 | *.suo
12 | *.cache
13 | *.docstates
14 | _ReSharper.*
15 | nuget.exe
16 | *net45.csproj
17 | *k10.csproj
18 | *.psess
19 | *.vsp
20 | *.pidb
21 | *.userprefs
22 | *DS_Store
23 | *.ncrunchsolution
24 | *.*sdf
25 | *.ipch
26 | .vs/
27 | project.lock.json
28 | bower_components/
29 | node_modules/
30 | tests/
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | # **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.publishsettings
221 | orleans.codegen.cs
222 |
223 | # Including strong name files can present a security risk
224 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
225 | #*.snk
226 |
227 | # Since there are multiple workflows, uncomment next line to ignore bower_components
228 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
229 | #bower_components/
230 |
231 | # RIA/Silverlight projects
232 | Generated_Code/
233 |
234 | # Backup & report files from converting an old project file
235 | # to a newer Visual Studio version. Backup files are not needed,
236 | # because we have git ;-)
237 | _UpgradeReport_Files/
238 | Backup*/
239 | UpgradeLog*.XML
240 | UpgradeLog*.htm
241 | ServiceFabricBackup/
242 | *.rptproj.bak
243 |
244 | # SQL Server files
245 | *.mdf
246 | *.ldf
247 | *.ndf
248 |
249 | # Business Intelligence projects
250 | *.rdl.data
251 | *.bim.layout
252 | *.bim_*.settings
253 | *.rptproj.rsuser
254 |
255 | # Microsoft Fakes
256 | FakesAssemblies/
257 |
258 | # GhostDoc plugin setting file
259 | *.GhostDoc.xml
260 |
261 | # Node.js Tools for Visual Studio
262 | .ntvs_analysis.dat
263 | node_modules/
264 |
265 | # Visual Studio 6 build log
266 | *.plg
267 |
268 | # Visual Studio 6 workspace options file
269 | *.opt
270 |
271 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
272 | *.vbw
273 |
274 | # Visual Studio LightSwitch build output
275 | **/*.HTMLClient/GeneratedArtifacts
276 | **/*.DesktopClient/GeneratedArtifacts
277 | **/*.DesktopClient/ModelManifest.xml
278 | **/*.Server/GeneratedArtifacts
279 | **/*.Server/ModelManifest.xml
280 | _Pvt_Extensions
281 |
282 | # Paket dependency manager
283 | .paket/paket.exe
284 | paket-files/
285 |
286 | # FAKE - F# Make
287 | .fake/
288 |
289 | # JetBrains Rider
290 | .idea/
291 | *.sln.iml
292 |
293 | # CodeRush
294 | .cr/
295 |
296 | # Python Tools for Visual Studio (PTVS)
297 | __pycache__/
298 | *.pyc
299 |
300 | # Cake - Uncomment if you are using it
301 | # tools/**
302 | # !tools/packages.config
303 |
304 | # Tabs Studio
305 | *.tss
306 |
307 | # Telerik's JustMock configuration file
308 | *.jmconfig
309 |
310 | # BizTalk build output
311 | *.btp.cs
312 | *.btm.cs
313 | *.odx.cs
314 | *.xsd.cs
315 |
316 | # OpenCover UI analysis results
317 | OpenCover/
318 |
319 | # Azure Stream Analytics local run output
320 | ASALocalRun/
321 |
322 | # MSBuild Binary and Structured Log
323 | *.binlog
324 |
325 | # NVidia Nsight GPU debugger configuration file
326 | *.nvuser
327 |
328 | # MFractors (Xamarin productivity tool) working folder
329 | .mfractor/
330 |
331 | logs/
332 |
333 | .vscode/
334 |
335 | .DS_Store
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
2 | WORKDIR /app
3 | COPY . .
4 | RUN dotnet publish src/Bootstrapper/NPay.Bootstrapper -c release -o out
5 |
6 | FROM mcr.microsoft.com/dotnet/aspnet:5.0
7 | WORKDIR /app
8 | COPY --from=build /app/out .
9 | ENV ASPNETCORE_URLS http://*:80
10 | ENV ASPNETCORE_ENVIRONMENT docker
11 | ENTRYPOINT dotnet NPay.Bootstrapper.dll
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 DevMentors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NPay.rest:
--------------------------------------------------------------------------------
1 | @url = http://localhost:5000
2 |
3 | ###
4 | GET {{url}}
--------------------------------------------------------------------------------
/NPay.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bootstrapper", "Bootstrapper", "{48607D4E-25F8-467B-BF0B-C0638D8BDFDC}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{6D99FF9F-068C-43B2-8849-C49929B87109}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{2EBB0D23-40E6-4664-8C20-D6E37718D15E}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Users", "Users", "{3D538A52-DE28-4079-AEE1-F8A992753E38}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Notifications", "Notifications", "{4F23B6D4-C6A3-44E2-829C-E6EC01D130CF}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wallets", "Wallets", "{6811A758-296C-49FF-A2DD-7A5EB3799E0D}"
17 | EndProject
18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Bootstrapper", "src\Bootstrapper\NPay.Bootstrapper\NPay.Bootstrapper.csproj", "{28B29BD4-2CD0-461D-ADE6-17284AB6936F}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Notifications.Api", "src\Modules\Notifications\NPay.Modules.Notifications.Api\NPay.Modules.Notifications.Api.csproj", "{B2716F1E-2211-4673-891D-67E3F7D7D1F7}"
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Users.Api", "src\Modules\Users\NPay.Modules.Users.Api\NPay.Modules.Users.Api.csproj", "{44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}"
23 | EndProject
24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Users.Core", "src\Modules\Users\NPay.Modules.Users.Core\NPay.Modules.Users.Core.csproj", "{A6CAE675-D96D-4C49-9360-3588DAE3DE8B}"
25 | EndProject
26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Users.Shared", "src\Modules\Users\NPay.Modules.Users.Shared\NPay.Modules.Users.Shared.csproj", "{61EDB434-2A83-4C5E-A675-80B91B251E01}"
27 | EndProject
28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Wallets.Api", "src\Modules\Wallets\NPay.Modules.Wallets.Api\NPay.Modules.Wallets.Api.csproj", "{ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}"
29 | EndProject
30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Wallets.Application", "src\Modules\Wallets\NPay.Modules.Wallets.Application\NPay.Modules.Wallets.Application.csproj", "{D3E56B79-0400-40EA-B716-30549C078FCB}"
31 | EndProject
32 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Wallets.Core", "src\Modules\Wallets\NPay.Modules.Wallets.Core\NPay.Modules.Wallets.Core.csproj", "{92E2A846-B990-4795-B8EA-EAC31E7FE205}"
33 | EndProject
34 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Wallets.Infrastructure", "src\Modules\Wallets\NPay.Modules.Wallets.Infrastructure\NPay.Modules.Wallets.Infrastructure.csproj", "{8AD07D64-218C-48F8-9016-7EA8848F6C9A}"
35 | EndProject
36 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Wallets.Shared", "src\Modules\Wallets\NPay.Modules.Wallets.Shared\NPay.Modules.Wallets.Shared.csproj", "{1F18212C-20D6-4AFD-A64B-5917BB37BCE9}"
37 | EndProject
38 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Shared", "src\Shared\NPay.Shared\NPay.Shared.csproj", "{B3DD62E9-B9E2-4088-8700-415B011590A4}"
39 | EndProject
40 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPay.Modules.Notifications.Shared", "src\Modules\Notifications\NPay.Modules.Notifications.Shared\NPay.Modules.Notifications.Shared.csproj", "{EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}"
41 | EndProject
42 | Global
43 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
44 | Debug|Any CPU = Debug|Any CPU
45 | Debug|x64 = Debug|x64
46 | Debug|x86 = Debug|x86
47 | Release|Any CPU = Release|Any CPU
48 | Release|x64 = Release|x64
49 | Release|x86 = Release|x86
50 | EndGlobalSection
51 | GlobalSection(SolutionProperties) = preSolution
52 | HideSolutionNode = FALSE
53 | EndGlobalSection
54 | GlobalSection(NestedProjects) = preSolution
55 | {3D538A52-DE28-4079-AEE1-F8A992753E38} = {6D99FF9F-068C-43B2-8849-C49929B87109}
56 | {4F23B6D4-C6A3-44E2-829C-E6EC01D130CF} = {6D99FF9F-068C-43B2-8849-C49929B87109}
57 | {6811A758-296C-49FF-A2DD-7A5EB3799E0D} = {6D99FF9F-068C-43B2-8849-C49929B87109}
58 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F} = {48607D4E-25F8-467B-BF0B-C0638D8BDFDC}
59 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7} = {4F23B6D4-C6A3-44E2-829C-E6EC01D130CF}
60 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF} = {3D538A52-DE28-4079-AEE1-F8A992753E38}
61 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B} = {3D538A52-DE28-4079-AEE1-F8A992753E38}
62 | {61EDB434-2A83-4C5E-A675-80B91B251E01} = {3D538A52-DE28-4079-AEE1-F8A992753E38}
63 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5} = {6811A758-296C-49FF-A2DD-7A5EB3799E0D}
64 | {D3E56B79-0400-40EA-B716-30549C078FCB} = {6811A758-296C-49FF-A2DD-7A5EB3799E0D}
65 | {92E2A846-B990-4795-B8EA-EAC31E7FE205} = {6811A758-296C-49FF-A2DD-7A5EB3799E0D}
66 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A} = {6811A758-296C-49FF-A2DD-7A5EB3799E0D}
67 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9} = {6811A758-296C-49FF-A2DD-7A5EB3799E0D}
68 | {B3DD62E9-B9E2-4088-8700-415B011590A4} = {2EBB0D23-40E6-4664-8C20-D6E37718D15E}
69 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF} = {4F23B6D4-C6A3-44E2-829C-E6EC01D130CF}
70 | EndGlobalSection
71 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
72 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
73 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Debug|Any CPU.Build.0 = Debug|Any CPU
74 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Debug|x64.ActiveCfg = Debug|Any CPU
75 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Debug|x64.Build.0 = Debug|Any CPU
76 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Debug|x86.ActiveCfg = Debug|Any CPU
77 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Debug|x86.Build.0 = Debug|Any CPU
78 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Release|Any CPU.ActiveCfg = Release|Any CPU
79 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Release|Any CPU.Build.0 = Release|Any CPU
80 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Release|x64.ActiveCfg = Release|Any CPU
81 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Release|x64.Build.0 = Release|Any CPU
82 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Release|x86.ActiveCfg = Release|Any CPU
83 | {28B29BD4-2CD0-461D-ADE6-17284AB6936F}.Release|x86.Build.0 = Release|Any CPU
84 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
85 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
86 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Debug|x64.ActiveCfg = Debug|Any CPU
87 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Debug|x64.Build.0 = Debug|Any CPU
88 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Debug|x86.ActiveCfg = Debug|Any CPU
89 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Debug|x86.Build.0 = Debug|Any CPU
90 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
91 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Release|Any CPU.Build.0 = Release|Any CPU
92 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Release|x64.ActiveCfg = Release|Any CPU
93 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Release|x64.Build.0 = Release|Any CPU
94 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Release|x86.ActiveCfg = Release|Any CPU
95 | {B2716F1E-2211-4673-891D-67E3F7D7D1F7}.Release|x86.Build.0 = Release|Any CPU
96 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
97 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
98 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Debug|x64.ActiveCfg = Debug|Any CPU
99 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Debug|x64.Build.0 = Debug|Any CPU
100 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Debug|x86.ActiveCfg = Debug|Any CPU
101 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Debug|x86.Build.0 = Debug|Any CPU
102 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
103 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Release|Any CPU.Build.0 = Release|Any CPU
104 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Release|x64.ActiveCfg = Release|Any CPU
105 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Release|x64.Build.0 = Release|Any CPU
106 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Release|x86.ActiveCfg = Release|Any CPU
107 | {44FBAC9C-7F39-496F-9BD0-B88A18D3B0FF}.Release|x86.Build.0 = Release|Any CPU
108 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
109 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
110 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Debug|x64.ActiveCfg = Debug|Any CPU
111 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Debug|x64.Build.0 = Debug|Any CPU
112 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Debug|x86.ActiveCfg = Debug|Any CPU
113 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Debug|x86.Build.0 = Debug|Any CPU
114 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
115 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Release|Any CPU.Build.0 = Release|Any CPU
116 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Release|x64.ActiveCfg = Release|Any CPU
117 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Release|x64.Build.0 = Release|Any CPU
118 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Release|x86.ActiveCfg = Release|Any CPU
119 | {A6CAE675-D96D-4C49-9360-3588DAE3DE8B}.Release|x86.Build.0 = Release|Any CPU
120 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
121 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Debug|Any CPU.Build.0 = Debug|Any CPU
122 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Debug|x64.ActiveCfg = Debug|Any CPU
123 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Debug|x64.Build.0 = Debug|Any CPU
124 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Debug|x86.ActiveCfg = Debug|Any CPU
125 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Debug|x86.Build.0 = Debug|Any CPU
126 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Release|Any CPU.ActiveCfg = Release|Any CPU
127 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Release|Any CPU.Build.0 = Release|Any CPU
128 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Release|x64.ActiveCfg = Release|Any CPU
129 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Release|x64.Build.0 = Release|Any CPU
130 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Release|x86.ActiveCfg = Release|Any CPU
131 | {61EDB434-2A83-4C5E-A675-80B91B251E01}.Release|x86.Build.0 = Release|Any CPU
132 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
133 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
134 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Debug|x64.ActiveCfg = Debug|Any CPU
135 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Debug|x64.Build.0 = Debug|Any CPU
136 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Debug|x86.ActiveCfg = Debug|Any CPU
137 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Debug|x86.Build.0 = Debug|Any CPU
138 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
139 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Release|Any CPU.Build.0 = Release|Any CPU
140 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Release|x64.ActiveCfg = Release|Any CPU
141 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Release|x64.Build.0 = Release|Any CPU
142 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Release|x86.ActiveCfg = Release|Any CPU
143 | {ACB82BC8-761E-42CF-9F56-E0CB9B634AE5}.Release|x86.Build.0 = Release|Any CPU
144 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
145 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
146 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Debug|x64.ActiveCfg = Debug|Any CPU
147 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Debug|x64.Build.0 = Debug|Any CPU
148 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Debug|x86.ActiveCfg = Debug|Any CPU
149 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Debug|x86.Build.0 = Debug|Any CPU
150 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
151 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Release|Any CPU.Build.0 = Release|Any CPU
152 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Release|x64.ActiveCfg = Release|Any CPU
153 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Release|x64.Build.0 = Release|Any CPU
154 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Release|x86.ActiveCfg = Release|Any CPU
155 | {D3E56B79-0400-40EA-B716-30549C078FCB}.Release|x86.Build.0 = Release|Any CPU
156 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
157 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Debug|Any CPU.Build.0 = Debug|Any CPU
158 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Debug|x64.ActiveCfg = Debug|Any CPU
159 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Debug|x64.Build.0 = Debug|Any CPU
160 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Debug|x86.ActiveCfg = Debug|Any CPU
161 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Debug|x86.Build.0 = Debug|Any CPU
162 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Release|Any CPU.ActiveCfg = Release|Any CPU
163 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Release|Any CPU.Build.0 = Release|Any CPU
164 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Release|x64.ActiveCfg = Release|Any CPU
165 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Release|x64.Build.0 = Release|Any CPU
166 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Release|x86.ActiveCfg = Release|Any CPU
167 | {92E2A846-B990-4795-B8EA-EAC31E7FE205}.Release|x86.Build.0 = Release|Any CPU
168 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
169 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
170 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Debug|x64.ActiveCfg = Debug|Any CPU
171 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Debug|x64.Build.0 = Debug|Any CPU
172 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Debug|x86.ActiveCfg = Debug|Any CPU
173 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Debug|x86.Build.0 = Debug|Any CPU
174 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
175 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Release|Any CPU.Build.0 = Release|Any CPU
176 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Release|x64.ActiveCfg = Release|Any CPU
177 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Release|x64.Build.0 = Release|Any CPU
178 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Release|x86.ActiveCfg = Release|Any CPU
179 | {8AD07D64-218C-48F8-9016-7EA8848F6C9A}.Release|x86.Build.0 = Release|Any CPU
180 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
181 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
182 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Debug|x64.ActiveCfg = Debug|Any CPU
183 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Debug|x64.Build.0 = Debug|Any CPU
184 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Debug|x86.ActiveCfg = Debug|Any CPU
185 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Debug|x86.Build.0 = Debug|Any CPU
186 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
187 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Release|Any CPU.Build.0 = Release|Any CPU
188 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Release|x64.ActiveCfg = Release|Any CPU
189 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Release|x64.Build.0 = Release|Any CPU
190 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Release|x86.ActiveCfg = Release|Any CPU
191 | {1F18212C-20D6-4AFD-A64B-5917BB37BCE9}.Release|x86.Build.0 = Release|Any CPU
192 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
193 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
194 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Debug|x64.ActiveCfg = Debug|Any CPU
195 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Debug|x64.Build.0 = Debug|Any CPU
196 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Debug|x86.ActiveCfg = Debug|Any CPU
197 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Debug|x86.Build.0 = Debug|Any CPU
198 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
199 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Release|Any CPU.Build.0 = Release|Any CPU
200 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Release|x64.ActiveCfg = Release|Any CPU
201 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Release|x64.Build.0 = Release|Any CPU
202 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Release|x86.ActiveCfg = Release|Any CPU
203 | {B3DD62E9-B9E2-4088-8700-415B011590A4}.Release|x86.Build.0 = Release|Any CPU
204 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
205 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
206 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Debug|x64.ActiveCfg = Debug|Any CPU
207 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Debug|x64.Build.0 = Debug|Any CPU
208 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Debug|x86.ActiveCfg = Debug|Any CPU
209 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Debug|x86.Build.0 = Debug|Any CPU
210 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
211 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Release|Any CPU.Build.0 = Release|Any CPU
212 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Release|x64.ActiveCfg = Release|Any CPU
213 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Release|x64.Build.0 = Release|Any CPU
214 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Release|x86.ActiveCfg = Release|Any CPU
215 | {EDFFD275-60CD-4AC3-B8DB-0CBDC833E0CF}.Release|x86.Build.0 = Release|Any CPU
216 | EndGlobalSection
217 | EndGlobal
218 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NPay
2 |
3 | ## About
4 |
5 | NPay is a simple virtual payments app built as a modular monolith.
6 |
7 | **How to start the solution?**
8 | ----------------
9 |
10 | Start the infrastructure using [Docker](https://docs.docker.com/get-docker/):
11 |
12 | ```
13 | docker-compose up -d
14 | ```
15 |
16 | Start API located under Bootstrapper project:
17 |
18 | ```
19 | cd src/Bootstrapper/NPay.Bootstrapper
20 | dotnet run
21 | ```
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | postgres:
5 | image: postgres
6 | shm_size: '4gb'
7 | container_name: postgres
8 | restart: unless-stopped
9 | environment:
10 | - POSTGRES_HOST_AUTH_METHOD=trust
11 | ports:
12 | - 5432:5432
13 | volumes:
14 | - postgres:/var/lib/postgresql/data
15 |
16 | volumes:
17 | postgres:
18 | driver: local
--------------------------------------------------------------------------------
/src/Bootstrapper/NPay.Bootstrapper/NPay.Bootstrapper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Bootstrapper/NPay.Bootstrapper/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Http;
3 | using NPay.Modules.Notifications.Api;
4 | using NPay.Modules.Users.Api;
5 | using NPay.Modules.Wallets.Api;
6 | using NPay.Shared;
7 |
8 | var builder = WebApplication
9 | .CreateBuilder(args);
10 |
11 | builder.Services
12 | .AddNotificationsModule()
13 | .AddUsersModule()
14 | .AddWalletsModule()
15 | .AddSharedFramework(builder.Configuration);
16 |
17 | var app = builder.Build();
18 |
19 | app.UseSharedFramework();
20 | app.UseNotificationsModule();
21 | app.UseUsersModule();
22 | app.UseWalletsModule();
23 | app.UseEndpoints(endpoints =>
24 | {
25 | endpoints.MapControllers();
26 | endpoints.MapGet("/", ctx => ctx.Response.WriteAsync("NPay API"));
27 | });
28 |
29 | app.Run();
--------------------------------------------------------------------------------
/src/Bootstrapper/NPay.Bootstrapper/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:45389",
7 | "sslPort": 44304
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": false,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "development"
16 | }
17 | },
18 | "NPay.Bootstrapper": {
19 | "commandName": "Project",
20 | "dotnetRunMessages": "true",
21 | "launchBrowser": false,
22 | "applicationUrl": "http://localhost:5000",
23 | "environmentVariables": {
24 | "ASPNETCORE_ENVIRONMENT": "development"
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Bootstrapper/NPay.Bootstrapper/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.Extensions.Configuration;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using NPay.Modules.Notifications.Api;
7 | using NPay.Modules.Users.Api;
8 | using NPay.Modules.Wallets.Api;
9 | using NPay.Shared;
10 |
11 | namespace NPay.Bootstrapper;
12 |
13 | public class Startup
14 | {
15 | private readonly IConfiguration _configuration;
16 |
17 | public Startup(IConfiguration configuration)
18 | {
19 | _configuration = configuration;
20 | }
21 |
22 | public void ConfigureServices(IServiceCollection services)
23 | {
24 | services.AddNotificationsModule();
25 | services.AddUsersModule();
26 | services.AddWalletsModule();
27 | services.AddSharedFramework(_configuration);
28 | }
29 |
30 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
31 | {
32 | app.UseSharedFramework();
33 | app.UseNotificationsModule();
34 | app.UseUsersModule();
35 | app.UseWalletsModule();
36 | app.UseEndpoints(endpoints =>
37 | {
38 | endpoints.MapControllers();
39 | endpoints.MapGet("/", ctx => ctx.Response.WriteAsync("NPay API"));
40 | });
41 | }
42 | }
--------------------------------------------------------------------------------
/src/Bootstrapper/NPay.Bootstrapper/appsettings.development.json:
--------------------------------------------------------------------------------
1 | {
2 | "logging": {
3 | "logLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Information",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Bootstrapper/NPay.Bootstrapper/appsettings.docker.json:
--------------------------------------------------------------------------------
1 | {
2 | "postgres": {
3 | "connectionString": "Host=postgres;Database=npay;Username=postgres;Password="
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/Bootstrapper/NPay.Bootstrapper/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "postgres": {
3 | "connectionString": "Host=localhost;Database=npay;Username=postgres;Password="
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using NPay.Modules.Notifications.Api.Services;
4 | using NPay.Modules.Notifications.Shared;
5 |
6 | namespace NPay.Modules.Notifications.Api;
7 |
8 | public static class Extensions
9 | {
10 | public static IServiceCollection AddNotificationsModule(this IServiceCollection services)
11 | {
12 | services.AddTransient();
13 | services.AddSingleton();
14 | services.AddSingleton();
15 |
16 | return services;
17 | }
18 |
19 | public static IApplicationBuilder UseNotificationsModule(this IApplicationBuilder app)
20 | {
21 | return app;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Handlers/Owners/OwnerVerifiedHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Notifications.Api.Services;
4 | using NPay.Modules.Wallets.Shared.Events;
5 | using NPay.Shared.Events;
6 |
7 | namespace NPay.Modules.Notifications.Api.Handlers.Owners;
8 |
9 | internal sealed class OwnerVerifiedHandler : IEventHandler
10 | {
11 | private readonly IEmailSender _emailSender;
12 | private readonly IEmailResolver _emailResolver;
13 |
14 | public OwnerVerifiedHandler(IEmailSender emailSender, IEmailResolver emailResolver)
15 | {
16 | _emailSender = emailSender;
17 | _emailResolver = emailResolver;
18 | }
19 |
20 | public Task HandleAsync(OwnerVerified @event, CancellationToken cancellationToken = default)
21 | => _emailSender.SendAsync(_emailResolver.GetForOwner(@event.OwnerId), "owner_verified");
22 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Handlers/Users/UserCreatedHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Notifications.Api.Services;
4 | using NPay.Modules.Users.Shared.Events;
5 | using NPay.Shared.Events;
6 |
7 | namespace NPay.Modules.Notifications.Api.Handlers.Users;
8 |
9 | internal sealed class UserCreatedHandler : IEventHandler
10 | {
11 | private readonly IEmailSender _emailSender;
12 |
13 | public UserCreatedHandler(IEmailSender emailSender)
14 | {
15 | _emailSender = emailSender;
16 | }
17 |
18 | public Task HandleAsync(UserCreated @event, CancellationToken cancellationToken = default)
19 | => _emailSender.SendAsync(@event.Email, "account_created");
20 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Handlers/Users/UserVerifiedHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Notifications.Api.Services;
4 | using NPay.Modules.Users.Shared.Events;
5 | using NPay.Shared.Events;
6 |
7 | namespace NPay.Modules.Notifications.Api.Handlers.Users;
8 |
9 | internal sealed class UserVerifiedHandler : IEventHandler
10 | {
11 | private readonly IEmailSender _emailSender;
12 |
13 | public UserVerifiedHandler(IEmailSender emailSender)
14 | {
15 | _emailSender = emailSender;
16 | }
17 |
18 | public Task HandleAsync(UserVerified @event, CancellationToken cancellationToken = default)
19 | => _emailSender.SendAsync(@event.Email, "account_verified");
20 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Handlers/Wallets/FundsAddedHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Notifications.Api.Services;
4 | using NPay.Modules.Wallets.Shared.Events;
5 | using NPay.Shared.Events;
6 |
7 | namespace NPay.Modules.Notifications.Api.Handlers.Wallets;
8 |
9 | internal sealed class FundsAddedHandler : IEventHandler
10 | {
11 | private readonly IEmailSender _emailSender;
12 | private readonly IEmailResolver _emailResolver;
13 |
14 | public FundsAddedHandler(IEmailSender emailSender, IEmailResolver emailResolver)
15 | {
16 | _emailSender = emailSender;
17 | _emailResolver = emailResolver;
18 | }
19 |
20 | public Task HandleAsync(FundsAdded @event, CancellationToken cancellationToken = default)
21 | => _emailSender.SendAsync(_emailResolver.GetForOwner(@event.OwnerId), "funds_added");
22 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Handlers/Wallets/FundsTransferredHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Notifications.Api.Services;
4 | using NPay.Modules.Wallets.Shared.Events;
5 | using NPay.Shared.Events;
6 |
7 | namespace NPay.Modules.Notifications.Api.Handlers.Wallets;
8 |
9 | internal sealed class FundsTransferredHandler : IEventHandler
10 | {
11 | private readonly IEmailSender _emailSender;
12 | private readonly IEmailResolver _emailResolver;
13 |
14 | public FundsTransferredHandler(IEmailSender emailSender, IEmailResolver emailResolver)
15 | {
16 | _emailSender = emailSender;
17 | _emailResolver = emailResolver;
18 | }
19 |
20 | public Task HandleAsync(FundsTransferred @event, CancellationToken cancellationToken = default)
21 | => Task.WhenAll(_emailSender.SendAsync(_emailResolver.GetForOwner(@event.FromWalletId), "funds_deducted"),
22 | _emailSender.SendAsync(_emailResolver.GetForOwner(@event.ToWalletId), "funds_added"));
23 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Handlers/Wallets/WalletAddedHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Notifications.Api.Services;
4 | using NPay.Modules.Wallets.Shared.Events;
5 | using NPay.Shared.Events;
6 |
7 | namespace NPay.Modules.Notifications.Api.Handlers.Wallets;
8 |
9 | internal sealed class WalletAddedHandler : IEventHandler
10 | {
11 | private readonly IEmailSender _emailSender;
12 | private readonly IEmailResolver _emailResolver;
13 |
14 | public WalletAddedHandler(IEmailSender emailSender, IEmailResolver emailResolver)
15 | {
16 | _emailSender = emailSender;
17 | _emailResolver = emailResolver;
18 | }
19 |
20 | public Task HandleAsync(WalletAdded @event, CancellationToken cancellationToken = default)
21 | => _emailSender.SendAsync(_emailResolver.GetForOwner(@event.OwnerId), "wallet_added");
22 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/NPay.Modules.Notifications.Api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Services/EmailResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NPay.Modules.Notifications.Api.Services;
4 |
5 | internal sealed class EmailResolver : IEmailResolver
6 | {
7 | // TODO: Resolve the user/owner email address from other modules
8 | public string GetForOwner(Guid ownerId) => $"{ownerId:N}@npay.io";
9 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Services/EmailSender.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.Extensions.Logging;
3 |
4 | namespace NPay.Modules.Notifications.Api.Services;
5 |
6 | internal sealed class EmailSender : IEmailSender
7 | {
8 | private readonly ILogger _logger;
9 |
10 | public EmailSender(ILogger logger)
11 | {
12 | _logger = logger;
13 | }
14 |
15 | public Task SendAsync(string receiver, string template)
16 | {
17 | // TODO: Implement an email sender
18 | _logger.LogInformation($"Sending an email to: '{receiver}', template: '{template}'...");
19 | return Task.CompletedTask;
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Services/IEmailResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NPay.Modules.Notifications.Api.Services;
4 |
5 | internal interface IEmailResolver
6 | {
7 | string GetForOwner(Guid ownerId);
8 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Services/IEmailSender.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace NPay.Modules.Notifications.Api.Services;
4 |
5 | internal interface IEmailSender
6 | {
7 | Task SendAsync(string receiver, string template);
8 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Api/Services/NotificationsModuleApi.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using NPay.Modules.Notifications.Shared;
3 |
4 | namespace NPay.Modules.Notifications.Api.Services;
5 |
6 | internal sealed class NotificationsModuleApi : INotificationsModuleApi
7 | {
8 | private readonly IEmailSender _emailSender;
9 |
10 | public NotificationsModuleApi(IEmailSender emailSender)
11 | {
12 | _emailSender = emailSender;
13 | }
14 |
15 | public Task SendEmailAsync(string receiver, string template)
16 | => _emailSender.SendAsync(receiver, template);
17 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Shared/INotificationsModuleApi.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace NPay.Modules.Notifications.Shared;
4 |
5 | public interface INotificationsModuleApi
6 | {
7 | Task SendEmailAsync(string receiver, string template);
8 | }
--------------------------------------------------------------------------------
/src/Modules/Notifications/NPay.Modules.Notifications.Shared/NPay.Modules.Notifications.Shared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Api/Controllers/UsersController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.AspNetCore.Mvc;
6 | using NPay.Modules.Users.Core.Services;
7 | using NPay.Modules.Users.Shared.DTO;
8 | using Swashbuckle.AspNetCore.Annotations;
9 |
10 | namespace NPay.Modules.Users.Api.Controllers;
11 |
12 | [ApiController]
13 | [Route("[controller]")]
14 | public class UsersController : ControllerBase
15 | {
16 | private readonly IUsersService _usersService;
17 |
18 | public UsersController(IUsersService usersService)
19 | {
20 | _usersService = usersService;
21 | }
22 |
23 | [HttpGet]
24 | [SwaggerOperation("Browse users")]
25 | [ProducesResponseType(StatusCodes.Status200OK)]
26 | public async Task>> Get()
27 | => Ok(await _usersService.BrowseAsync());
28 |
29 | [HttpGet("{userId:guid}")]
30 | [SwaggerOperation("Get user by ID")]
31 | [ProducesResponseType(StatusCodes.Status200OK)]
32 | [ProducesResponseType(StatusCodes.Status404NotFound)]
33 | public async Task> Get(Guid userId)
34 | {
35 | var user = await _usersService.GetAsync(userId);
36 | if (user is not null)
37 | {
38 | return Ok(user);
39 | }
40 |
41 | return NotFound();
42 | }
43 |
44 | [HttpGet("by-email/{email}")]
45 | [SwaggerOperation("Get user by email")]
46 | [ProducesResponseType(StatusCodes.Status200OK)]
47 | [ProducesResponseType(StatusCodes.Status404NotFound)]
48 | public async Task> Get(string email)
49 | {
50 | var user = await _usersService.GetAsync(email);
51 | if (user is not null)
52 | {
53 | return Ok(user);
54 | }
55 |
56 | return NotFound();
57 | }
58 |
59 | [HttpPost]
60 | [SwaggerOperation("Create user")]
61 | [ProducesResponseType(StatusCodes.Status201Created)]
62 | [ProducesResponseType(StatusCodes.Status400BadRequest)]
63 | public async Task Post(UserDetailsDto user)
64 | {
65 | user.UserId = Guid.NewGuid();
66 | await _usersService.AddAsync(user);
67 | return CreatedAtAction(nameof(Get), new { userId = user.UserId }, null);
68 | }
69 |
70 | [HttpPut("{userId:guid}/verify")]
71 | [SwaggerOperation("Verify user")]
72 | [ProducesResponseType(StatusCodes.Status204NoContent)]
73 | [ProducesResponseType(StatusCodes.Status400BadRequest)]
74 | public async Task Post(Guid userId)
75 | {
76 | await _usersService.VerifyAsync(userId);
77 | return NoContent();
78 | }
79 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Api/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using NPay.Modules.Users.Core;
4 |
5 | namespace NPay.Modules.Users.Api;
6 |
7 | public static class Extensions
8 | {
9 | public static IServiceCollection AddUsersModule(this IServiceCollection services)
10 | {
11 | services.AddCoreLayer();
12 |
13 | return services;
14 | }
15 |
16 | public static IApplicationBuilder UseUsersModule(this IApplicationBuilder app)
17 | {
18 | return app;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Api/NPay.Modules.Users.Api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/DAL/Configurations/UserConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
3 | using NPay.Modules.Users.Core.Entities;
4 |
5 | namespace NPay.Modules.Users.Core.DAL.Configurations;
6 |
7 | internal sealed class UserConfiguration : IEntityTypeConfiguration
8 | {
9 | public void Configure(EntityTypeBuilder builder)
10 | {
11 | builder.HasIndex(x => x.Email).IsUnique();
12 | builder.Property(x => x.Email).IsRequired();
13 | builder.Property(x => x.FullName).IsRequired().HasMaxLength(100);
14 | builder.Property(x => x.Address).IsRequired().HasMaxLength(200);
15 | builder.Property(x => x.Identity).IsRequired().HasMaxLength(40);
16 | builder.Property(x => x.Nationality).IsRequired().HasMaxLength(2);
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/DAL/Migrations/20211019160150_Users_Init.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Migrations;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 | using NPay.Modules.Users.Core.DAL;
8 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
9 |
10 | namespace NPay.Modules.Users.Core.DAL.Migrations
11 | {
12 | [DbContext(typeof(UsersDbContext))]
13 | [Migration("20211019160150_Users_Init")]
14 | partial class Users_Init
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasDefaultSchema("users")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 63)
22 | .HasAnnotation("ProductVersion", "5.0.11")
23 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
24 |
25 | modelBuilder.Entity("NPay.Modules.Users.Core.Entities.User", b =>
26 | {
27 | b.Property("Id")
28 | .ValueGeneratedOnAdd()
29 | .HasColumnType("uuid");
30 |
31 | b.Property("Address")
32 | .IsRequired()
33 | .HasMaxLength(200)
34 | .HasColumnType("character varying(200)");
35 |
36 | b.Property("CreatedAt")
37 | .HasColumnType("timestamp without time zone");
38 |
39 | b.Property("Email")
40 | .IsRequired()
41 | .HasColumnType("text");
42 |
43 | b.Property("FullName")
44 | .IsRequired()
45 | .HasMaxLength(100)
46 | .HasColumnType("character varying(100)");
47 |
48 | b.Property("Identity")
49 | .IsRequired()
50 | .HasMaxLength(40)
51 | .HasColumnType("character varying(40)");
52 |
53 | b.Property("Nationality")
54 | .IsRequired()
55 | .HasMaxLength(2)
56 | .HasColumnType("character varying(2)");
57 |
58 | b.Property("VerifiedAt")
59 | .HasColumnType("timestamp without time zone");
60 |
61 | b.HasKey("Id");
62 |
63 | b.HasIndex("Email")
64 | .IsUnique();
65 |
66 | b.ToTable("Users");
67 | });
68 | #pragma warning restore 612, 618
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/DAL/Migrations/20211019160150_Users_Init.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace NPay.Modules.Users.Core.DAL.Migrations
5 | {
6 | public partial class Users_Init : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.EnsureSchema(
11 | name: "users");
12 |
13 | migrationBuilder.CreateTable(
14 | name: "Users",
15 | schema: "users",
16 | columns: table => new
17 | {
18 | Id = table.Column(type: "uuid", nullable: false),
19 | Email = table.Column(type: "text", nullable: false),
20 | FullName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
21 | Address = table.Column(type: "character varying(200)", maxLength: 200, nullable: false),
22 | Nationality = table.Column(type: "character varying(2)", maxLength: 2, nullable: false),
23 | Identity = table.Column(type: "character varying(40)", maxLength: 40, nullable: false),
24 | CreatedAt = table.Column(type: "timestamp without time zone", nullable: false),
25 | VerifiedAt = table.Column(type: "timestamp without time zone", nullable: true)
26 | },
27 | constraints: table =>
28 | {
29 | table.PrimaryKey("PK_Users", x => x.Id);
30 | });
31 |
32 | migrationBuilder.CreateIndex(
33 | name: "IX_Users_Email",
34 | schema: "users",
35 | table: "Users",
36 | column: "Email",
37 | unique: true);
38 | }
39 |
40 | protected override void Down(MigrationBuilder migrationBuilder)
41 | {
42 | migrationBuilder.DropTable(
43 | name: "Users",
44 | schema: "users");
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/DAL/Migrations/UsersDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
6 | using NPay.Modules.Users.Core.DAL;
7 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
8 |
9 | namespace NPay.Modules.Users.Core.DAL.Migrations
10 | {
11 | [DbContext(typeof(UsersDbContext))]
12 | partial class UsersDbContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasDefaultSchema("users")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 63)
20 | .HasAnnotation("ProductVersion", "5.0.11")
21 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
22 |
23 | modelBuilder.Entity("NPay.Modules.Users.Core.Entities.User", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd()
27 | .HasColumnType("uuid");
28 |
29 | b.Property("Address")
30 | .IsRequired()
31 | .HasMaxLength(200)
32 | .HasColumnType("character varying(200)");
33 |
34 | b.Property("CreatedAt")
35 | .HasColumnType("timestamp without time zone");
36 |
37 | b.Property("Email")
38 | .IsRequired()
39 | .HasColumnType("text");
40 |
41 | b.Property("FullName")
42 | .IsRequired()
43 | .HasMaxLength(100)
44 | .HasColumnType("character varying(100)");
45 |
46 | b.Property("Identity")
47 | .IsRequired()
48 | .HasMaxLength(40)
49 | .HasColumnType("character varying(40)");
50 |
51 | b.Property("Nationality")
52 | .IsRequired()
53 | .HasMaxLength(2)
54 | .HasColumnType("character varying(2)");
55 |
56 | b.Property("VerifiedAt")
57 | .HasColumnType("timestamp without time zone");
58 |
59 | b.HasKey("Id");
60 |
61 | b.HasIndex("Email")
62 | .IsUnique();
63 |
64 | b.ToTable("Users");
65 | });
66 | #pragma warning restore 612, 618
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/DAL/UsersDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using NPay.Modules.Users.Core.Entities;
3 |
4 | namespace NPay.Modules.Users.Core.DAL;
5 |
6 | internal sealed class UsersDbContext : DbContext
7 | {
8 | public DbSet Users { get; set; }
9 |
10 | public UsersDbContext(DbContextOptions options) : base(options)
11 | {
12 | }
13 |
14 | protected override void OnModelCreating(ModelBuilder modelBuilder)
15 | {
16 | modelBuilder.HasDefaultSchema("users");
17 | modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Entities/User.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Modules.Users.Core.Exceptions;
3 |
4 | namespace NPay.Modules.Users.Core.Entities;
5 |
6 | internal sealed class User
7 | {
8 | public Guid Id { get; private set; }
9 | public string Email { get; private set; }
10 | public string FullName { get; private set; }
11 | public string Address { get; private set; }
12 | public string Nationality { get; private set; }
13 | public string Identity { get; private set; }
14 | public DateTime CreatedAt { get; private set; }
15 | public DateTime? VerifiedAt { get; private set; }
16 |
17 | private User()
18 | {
19 | }
20 |
21 | public User(Guid id, string email, string fullName, string address, string nationality, string identity,
22 | DateTime createdAt)
23 | {
24 | Id = id;
25 | Email = email;
26 | FullName = fullName;
27 | Address = address;
28 | Nationality = nationality;
29 | Identity = identity;
30 | CreatedAt = createdAt;
31 | }
32 |
33 | public void Verify(DateTime verifiedAt)
34 | {
35 | if (VerifiedAt.HasValue)
36 | {
37 | throw new UserAlreadyVerifiedException(Id);
38 | }
39 |
40 | VerifiedAt = verifiedAt;
41 | }
42 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Exceptions/InvalidAddressException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Users.Core.Exceptions;
4 |
5 | internal sealed class InvalidAddressException : NPayException
6 | {
7 | public string Address { get; }
8 |
9 | public InvalidAddressException(string address) : base($"Address: '{address}' is invalid.")
10 | {
11 | Address = address;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Exceptions/InvalidEmailException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Users.Core.Exceptions;
4 |
5 | internal sealed class InvalidEmailException : NPayException
6 | {
7 | public string Email { get; }
8 |
9 | public InvalidEmailException(string email) : base($"Email: '{email}' is invalid.")
10 | {
11 | Email = email;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Exceptions/InvalidFullNameException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Users.Core.Exceptions;
4 |
5 | internal sealed class InvalidFullNameException : NPayException
6 | {
7 | public string FullName { get; }
8 |
9 | public InvalidFullNameException(string fullName) : base($"Full name: '{fullName}' is invalid.")
10 | {
11 | FullName = fullName;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Exceptions/UserAlreadyExistsException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Users.Core.Exceptions;
4 |
5 | internal sealed class UserAlreadyExistsException : NPayException
6 | {
7 | public string Email { get; }
8 |
9 | public UserAlreadyExistsException(string email) : base($"User with email: '{email}' already exists.")
10 | {
11 | Email = email;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Exceptions/UserAlreadyVerifiedException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Exceptions;
3 |
4 | namespace NPay.Modules.Users.Core.Exceptions;
5 |
6 | internal sealed class UserAlreadyVerifiedException : NPayException
7 | {
8 | public Guid UserId { get; }
9 |
10 | public UserAlreadyVerifiedException(Guid userId) : base($"User with ID: '{userId}' is already verified.")
11 | {
12 | UserId = userId;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Exceptions/UserNotFoundException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Exceptions;
3 |
4 | namespace NPay.Modules.Users.Core.Exceptions;
5 |
6 | internal sealed class UserNotFoundException : NPayException
7 | {
8 | public Guid UserId { get; }
9 |
10 | public UserNotFoundException(Guid userId) : base($"User with ID: '{userId}' was not found.")
11 | {
12 | UserId = userId;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using NPay.Modules.Users.Core.DAL;
3 | using NPay.Modules.Users.Core.Services;
4 | using NPay.Modules.Users.Shared;
5 | using NPay.Shared.Database;
6 |
7 | namespace NPay.Modules.Users.Core;
8 |
9 | public static class Extensions
10 | {
11 | public static IServiceCollection AddCoreLayer(this IServiceCollection services)
12 | {
13 | services.AddPostgres();
14 | services.AddTransient();
15 | services.AddTransient();
16 |
17 | return services;
18 | }
19 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/NPay.Modules.Users.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Services/IUsersService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using NPay.Modules.Users.Shared.DTO;
5 |
6 | namespace NPay.Modules.Users.Core.Services;
7 |
8 | public interface IUsersService
9 | {
10 | Task GetAsync(Guid userId);
11 | Task GetAsync(string email);
12 | Task> BrowseAsync();
13 | Task AddAsync(UserDetailsDto dto);
14 | Task VerifyAsync(Guid userId);
15 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Services/UsersModuleApi.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Users.Shared;
4 | using NPay.Modules.Users.Shared.DTO;
5 |
6 | namespace NPay.Modules.Users.Core.Services;
7 |
8 | internal sealed class UsersModuleApi : IUsersModuleApi
9 | {
10 | private readonly IUsersService _usersService;
11 |
12 | public UsersModuleApi(IUsersService usersService)
13 | {
14 | _usersService = usersService;
15 | }
16 |
17 | public Task GetUserAsync(Guid userId) => _usersService.GetAsync(userId);
18 |
19 | public Task GetUserAsync(string email) => _usersService.GetAsync(email);
20 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Core/Services/UsersService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.Extensions.Logging;
7 | using NPay.Modules.Users.Core.DAL;
8 | using NPay.Modules.Users.Core.Entities;
9 | using NPay.Modules.Users.Core.Exceptions;
10 | using NPay.Modules.Users.Shared.DTO;
11 | using NPay.Modules.Users.Shared.Events;
12 | using NPay.Shared.Messaging;
13 | using NPay.Shared.Time;
14 |
15 | namespace NPay.Modules.Users.Core.Services;
16 |
17 | internal sealed class UsersService : IUsersService
18 | {
19 | private readonly UsersDbContext _dbContext;
20 | private readonly IMessageBroker _messageBroker;
21 | private readonly IClock _clock;
22 | private readonly ILogger _logger;
23 |
24 | public UsersService(UsersDbContext dbContext, IMessageBroker messageBroker, IClock clock,
25 | ILogger logger)
26 | {
27 | _dbContext = dbContext;
28 | _messageBroker = messageBroker;
29 | _clock = clock;
30 | _logger = logger;
31 | }
32 |
33 | public async Task GetAsync(Guid userId)
34 | {
35 | var user = await _dbContext.Users.SingleOrDefaultAsync(x => x.Id == userId);
36 |
37 | // TODO: Make use of custom mapping interface or AutoMapper, Mapster etc.
38 |
39 | return user is null ? null : MapToDetailsDto(user);
40 | }
41 |
42 | public async Task GetAsync(string email)
43 | {
44 | // TODO: Additional email validation
45 | email = email?.ToLowerInvariant();
46 | if (string.IsNullOrWhiteSpace(email))
47 | {
48 | throw new InvalidEmailException(email);
49 | }
50 |
51 | var user = await _dbContext.Users.SingleOrDefaultAsync(x => x.Email == email);
52 |
53 | return user is null ? null : MapToDetailsDto(user);
54 | }
55 |
56 | public async Task> BrowseAsync()
57 | {
58 | var users = await _dbContext.Users.ToListAsync();
59 |
60 | // TODO: Implement pagination, sorting etc.
61 |
62 | return users.Select(MapToDto).ToList();
63 | }
64 |
65 | public async Task AddAsync(UserDetailsDto dto)
66 | {
67 | var email = dto.Email.ToLowerInvariant();
68 | if (await _dbContext.Users.AnyAsync(x => x.Email == email))
69 | {
70 | throw new UserAlreadyExistsException(email);
71 | }
72 |
73 | // TODO: Additional validation for the remaining properties
74 | if (string.IsNullOrWhiteSpace(dto.FullName))
75 | {
76 | throw new InvalidFullNameException(dto.FullName);
77 | }
78 |
79 | if (string.IsNullOrWhiteSpace(dto.Address))
80 | {
81 | throw new InvalidAddressException(dto.Address);
82 | }
83 |
84 | var user = new User(dto.UserId, email, dto.FullName, dto.Address,
85 | dto.Nationality, dto.Identity, _clock.CurrentDate());
86 | await _dbContext.Users.AddAsync(user);
87 | await _dbContext.SaveChangesAsync();
88 | await _messageBroker.PublishAsync(new UserCreated(user.Id, user.Email, user.FullName, user.Nationality));
89 | _logger.LogInformation($"Created the user with ID: '{dto.UserId}'.");
90 | }
91 |
92 | public async Task VerifyAsync(Guid userId)
93 | {
94 | var user = await _dbContext.Users.SingleOrDefaultAsync(x => x.Id == userId);
95 | if (user is null)
96 | {
97 | throw new UserNotFoundException(userId);
98 | }
99 |
100 | user.Verify(_clock.CurrentDate());
101 | _dbContext.Users.Update(user);
102 | await _dbContext.SaveChangesAsync();
103 | await _messageBroker.PublishAsync(new UserVerified(user.Id, user.Email, user.Nationality));
104 | _logger.LogInformation($"Verified the user with ID: '{user.Id}'.");
105 | }
106 |
107 | // TODO: Extract to the dedicated "mapper" interfaces or extension methods
108 |
109 | private static UserDto MapToDto(User user)=> Map(user);
110 |
111 | private static UserDetailsDto MapToDetailsDto(User user)
112 | {
113 | var dto = Map(user);
114 | dto.Address = user.Address;
115 | dto.Identity = user.Identity;
116 |
117 | return dto;
118 | }
119 |
120 | private static T Map(User user) where T : UserDto, new()
121 | => new()
122 | {
123 | UserId = user.Id,
124 | Email = user.Email,
125 | FullName = user.FullName,
126 | Nationality = user.Nationality,
127 | State = user.VerifiedAt.HasValue ? "verified" : "unverified",
128 | CreatedAt = user.CreatedAt
129 | };
130 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Shared/DTO/UserDetailsDto.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace NPay.Modules.Users.Shared.DTO;
4 |
5 | public class UserDetailsDto : UserDto
6 | {
7 | [Required]
8 | public string Address { get; set; }
9 |
10 | [Required]
11 | public string Identity { get; set; }
12 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Shared/DTO/UserDto.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace NPay.Modules.Users.Shared.DTO;
5 |
6 | public class UserDto
7 | {
8 | public Guid UserId { get; set; }
9 |
10 | [Required]
11 | [EmailAddress]
12 | public string Email { get; set; }
13 |
14 | [Required]
15 | public string FullName { get; set; }
16 |
17 | [Required]
18 | public string Nationality { get; set; }
19 |
20 | public string State { get; set; }
21 | public DateTime CreatedAt { get; set; }
22 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Shared/Events/UserCreated.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Events;
3 |
4 | namespace NPay.Modules.Users.Shared.Events;
5 |
6 | public record UserCreated(Guid UserId, string Email, string FullName, string Nationality) : IEvent;
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Shared/Events/UserVerified.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Events;
3 |
4 | namespace NPay.Modules.Users.Shared.Events;
5 |
6 | public record UserVerified(Guid UserId, string Email, string Nationality) : IEvent;
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Shared/IUsersModuleApi.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Users.Shared.DTO;
4 |
5 | namespace NPay.Modules.Users.Shared;
6 |
7 | public interface IUsersModuleApi
8 | {
9 | Task GetUserAsync(Guid userId);
10 | Task GetUserAsync(string email);
11 | }
--------------------------------------------------------------------------------
/src/Modules/Users/NPay.Modules.Users.Shared/NPay.Modules.Users.Shared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Modules/Users/Users.rest:
--------------------------------------------------------------------------------
1 | @url = http://localhost:5000/users
2 | @email = user1@npay.io
3 | @userId = 00000000-0000-0000-0000-000000000000
4 |
5 | ###
6 | GET {{url}}
7 |
8 | ###
9 | GET {{url}}/{{userId}}
10 |
11 | ###
12 | GET {{url}}/by-email/{{email}}
13 |
14 | ###
15 | POST {{url}}
16 | Content-Type: application/json
17 |
18 | {
19 | "email": "{{email}}",
20 | "fullName": "John Doe",
21 | "address": "Test street 123",
22 | "nationality": "PL",
23 | "identity": "ID1234567890"
24 | }
25 |
26 | ###
27 | PUT {{url}}/{{userId}}/verify
28 | Content-Type: application/json
29 |
30 | {
31 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Api/Controllers/FundsController.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.AspNetCore.Mvc;
4 | using NPay.Modules.Wallets.Application.Wallets.Commands;
5 | using NPay.Shared.Dispatchers;
6 | using Swashbuckle.AspNetCore.Annotations;
7 |
8 | namespace NPay.Modules.Wallets.Api.Controllers;
9 |
10 | [ApiController]
11 | [Route("[controller]")]
12 | public class FundsController : ControllerBase
13 | {
14 | private readonly IDispatcher _dispatcher;
15 |
16 | public FundsController(IDispatcher dispatcher)
17 | {
18 | _dispatcher = dispatcher;
19 | }
20 |
21 | [HttpPost]
22 | [SwaggerOperation("Add funds")]
23 | [ProducesResponseType(StatusCodes.Status204NoContent)]
24 | [ProducesResponseType(StatusCodes.Status400BadRequest)]
25 | public async Task Post(AddFunds command)
26 | {
27 | await _dispatcher.SendAsync(command);
28 | return NoContent();
29 | }
30 |
31 | [HttpPost("transfer")]
32 | [SwaggerOperation("Transfer funds")]
33 | [ProducesResponseType(StatusCodes.Status204NoContent)]
34 | [ProducesResponseType(StatusCodes.Status400BadRequest)]
35 | public async Task Post(TransferFunds command)
36 | {
37 | await _dispatcher.SendAsync(command);
38 | return NoContent();
39 | }
40 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Api/Controllers/OwnersController.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.AspNetCore.Mvc;
4 | using NPay.Modules.Wallets.Application.Owners.Commands;
5 | using NPay.Shared.Dispatchers;
6 | using Swashbuckle.AspNetCore.Annotations;
7 |
8 | namespace NPay.Modules.Wallets.Api.Controllers;
9 |
10 | [ApiController]
11 | [Route("[controller]")]
12 | public class OwnersController : ControllerBase
13 | {
14 | private readonly IDispatcher _dispatcher;
15 |
16 | public OwnersController(IDispatcher dispatcher)
17 | {
18 | _dispatcher = dispatcher;
19 | }
20 |
21 | [HttpPost]
22 | [SwaggerOperation("Add owner")]
23 | [ProducesResponseType(StatusCodes.Status204NoContent)]
24 | [ProducesResponseType(StatusCodes.Status400BadRequest)]
25 | public async Task Post(AddOwner command)
26 | {
27 | await _dispatcher.SendAsync(command);
28 | return NoContent();
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Api/Controllers/WalletsController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.AspNetCore.Mvc;
5 | using NPay.Modules.Wallets.Application.Wallets.Commands;
6 | using NPay.Modules.Wallets.Application.Wallets.Queries;
7 | using NPay.Modules.Wallets.Shared.DTO;
8 | using NPay.Shared.Dispatchers;
9 | using Swashbuckle.AspNetCore.Annotations;
10 |
11 | namespace NPay.Modules.Wallets.Api.Controllers;
12 |
13 | [ApiController]
14 | [Route("[controller]")]
15 | public class WalletsController : ControllerBase
16 | {
17 | private readonly IDispatcher _dispatcher;
18 |
19 | public WalletsController(IDispatcher dispatcher)
20 | {
21 | _dispatcher = dispatcher;
22 | }
23 |
24 | [HttpGet("{walletId:guid}")]
25 | [SwaggerOperation("Get wallet")]
26 | [ProducesResponseType(StatusCodes.Status200OK)]
27 | [ProducesResponseType(StatusCodes.Status404NotFound)]
28 | public async Task> Get(Guid walletId)
29 | {
30 | var wallet = await _dispatcher.QueryAsync(new GetWallet { WalletId = walletId });
31 | if (wallet is not null)
32 | {
33 | return Ok(wallet);
34 | }
35 |
36 | return NotFound();
37 | }
38 |
39 | [HttpPost]
40 | [SwaggerOperation("Add wallet")]
41 | [ProducesResponseType(StatusCodes.Status201Created)]
42 | [ProducesResponseType(StatusCodes.Status400BadRequest)]
43 | public async Task Post(AddWallet command)
44 | {
45 | await _dispatcher.SendAsync(command);
46 | return CreatedAtAction(nameof(Get), new { walletId = command.WalletId }, null);
47 | }
48 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Api/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using NPay.Modules.Wallets.Application;
4 | using NPay.Modules.Wallets.Core;
5 | using NPay.Modules.Wallets.Infrastructure;
6 |
7 | namespace NPay.Modules.Wallets.Api;
8 |
9 | public static class Extensions
10 | {
11 | public static IServiceCollection AddWalletsModule(this IServiceCollection services)
12 | {
13 | services.AddCoreLayer();
14 | services.AddApplicationLayer();
15 | services.AddInfrastructureLayer();
16 |
17 | return services;
18 | }
19 |
20 | public static IApplicationBuilder UseWalletsModule(this IApplicationBuilder app)
21 | {
22 | return app;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Api/NPay.Modules.Wallets.Api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using NPay.Modules.Wallets.Application.Services;
4 | using NPay.Modules.Wallets.Shared;
5 |
6 | [assembly: InternalsVisibleTo("NPay.Modules.Wallets.Infrastructure")]
7 | namespace NPay.Modules.Wallets.Application;
8 |
9 | public static class Extensions
10 | {
11 | public static IServiceCollection AddApplicationLayer(this IServiceCollection services)
12 | {
13 | services.AddTransient();
14 |
15 | return services;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/NPay.Modules.Wallets.Application.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Owners/Commands/AddOwner.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Commands;
2 |
3 | namespace NPay.Modules.Wallets.Application.Owners.Commands;
4 |
5 | public record AddOwner(string Email) : ICommand;
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Owners/Commands/Handlers/AddOwnerHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Logging;
4 | using NPay.Modules.Users.Shared;
5 | using NPay.Modules.Wallets.Application.Owners.Exceptions;
6 | using NPay.Modules.Wallets.Core.Owners.Aggregates;
7 | using NPay.Modules.Wallets.Core.Owners.Repositories;
8 | using NPay.Shared.Commands;
9 | using NPay.Shared.Time;
10 |
11 | namespace NPay.Modules.Wallets.Application.Owners.Commands.Handlers;
12 |
13 | internal sealed class AddOwnerHandler : ICommandHandler
14 | {
15 | private readonly IOwnerRepository _ownerRepository;
16 | private readonly IUsersModuleApi _usersModuleApi;
17 | private readonly IClock _clock;
18 | private readonly ILogger _logger;
19 |
20 | public AddOwnerHandler(IOwnerRepository ownerRepository, IUsersModuleApi usersModuleApi, IClock clock,
21 | ILogger logger)
22 | {
23 | _ownerRepository = ownerRepository;
24 | _usersModuleApi = usersModuleApi;
25 | _clock = clock;
26 | _logger = logger;
27 | }
28 |
29 | public async Task HandleAsync(AddOwner command, CancellationToken cancellationToken = default)
30 | {
31 | var user = await _usersModuleApi.GetUserAsync(command.Email);
32 | if (user is null)
33 | {
34 | throw new UserNotFoundException(command.Email);
35 | }
36 |
37 | if (await _ownerRepository.GetAsync(user.UserId) is not null)
38 | {
39 | throw new OwnerAlreadyExistsException(command.Email);
40 | }
41 |
42 | var now = _clock.CurrentDate();
43 | var owner = new Owner(user.UserId, user.FullName, user.Nationality, now);
44 | await _ownerRepository.AddAsync(owner);
45 | _logger.LogInformation($"Created an owner for the user with ID: '{user.UserId}'.");
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Owners/Exceptions/UserAlreadyExistsException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Application.Owners.Exceptions;
4 |
5 | internal sealed class OwnerAlreadyExistsException : NPayException
6 | {
7 | public string Email { get; }
8 |
9 | public OwnerAlreadyExistsException(string email) : base($"Owner with email: '{email}' already exists.")
10 | {
11 | Email = email;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Owners/Exceptions/UserNotFoundException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Application.Owners.Exceptions;
4 |
5 | internal sealed class UserNotFoundException : NPayException
6 | {
7 | public string Email { get; }
8 |
9 | public UserNotFoundException(string email) : base($"User with email: '{email}' was not found.")
10 | {
11 | Email = email;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Owners/ExternalEvents/Handlers/UserCreatedHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Logging;
4 | using NPay.Modules.Users.Shared.Events;
5 | using NPay.Modules.Wallets.Application.Owners.Exceptions;
6 | using NPay.Modules.Wallets.Core.Owners.Aggregates;
7 | using NPay.Modules.Wallets.Core.Owners.Repositories;
8 | using NPay.Shared.Events;
9 | using NPay.Shared.Time;
10 |
11 | namespace NPay.Modules.Wallets.Application.Owners.ExternalEvents.Handlers;
12 |
13 | internal sealed class UserCreatedHandler : IEventHandler
14 | {
15 | private readonly IOwnerRepository _ownerRepository;
16 | private readonly IClock _clock;
17 | private readonly ILogger _logger;
18 |
19 | public UserCreatedHandler(IOwnerRepository ownerRepository, IClock clock, ILogger logger)
20 | {
21 | _ownerRepository = ownerRepository;
22 | _clock = clock;
23 | _logger = logger;
24 | }
25 |
26 | public async Task HandleAsync(UserCreated @event, CancellationToken cancellationToken = default)
27 | {
28 | if (await _ownerRepository.GetAsync(@event.UserId) is not null)
29 | {
30 | throw new OwnerAlreadyExistsException(@event.Email);
31 | }
32 |
33 | var now = _clock.CurrentDate();
34 | var owner = new Owner(@event.UserId, @event.FullName, @event.Nationality, now);
35 | await _ownerRepository.AddAsync(owner);
36 | _logger.LogInformation($"Created an owner for the user with ID: '{@event.UserId}'.");
37 | }
38 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Owners/ExternalEvents/Handlers/UserVerifiedHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Logging;
4 | using NPay.Modules.Users.Shared.Events;
5 | using NPay.Modules.Wallets.Core.Owners.Exceptions;
6 | using NPay.Modules.Wallets.Core.Owners.Repositories;
7 | using NPay.Modules.Wallets.Shared.Events;
8 | using NPay.Shared.Events;
9 | using NPay.Shared.Messaging;
10 | using NPay.Shared.Time;
11 |
12 | namespace NPay.Modules.Wallets.Application.Owners.ExternalEvents.Handlers;
13 |
14 | internal sealed class UserVerifiedHandler : IEventHandler
15 | {
16 | private readonly IOwnerRepository _ownerRepository;
17 | private readonly IMessageBroker _messageBroker;
18 | private readonly IClock _clock;
19 | private readonly ILogger _logger;
20 |
21 | public UserVerifiedHandler(IOwnerRepository ownerRepository, IMessageBroker messageBroker, IClock clock,
22 | ILogger logger)
23 | {
24 | _ownerRepository = ownerRepository;
25 | _messageBroker = messageBroker;
26 | _clock = clock;
27 | _logger = logger;
28 | }
29 |
30 | public async Task HandleAsync(UserVerified @event, CancellationToken cancellationToken = default)
31 | {
32 | var owner = await _ownerRepository.GetAsync(@event.UserId);
33 | if (owner is null)
34 | {
35 | throw new OwnerNotFoundException(@event.UserId);
36 | }
37 |
38 | var now = _clock.CurrentDate();
39 | owner.Verify(now);
40 | await _ownerRepository.UpdateAsync(owner);
41 | await _messageBroker.PublishAsync(new OwnerVerified(owner.Id), cancellationToken);
42 | _logger.LogInformation($"Verified an owner for the user with ID: '{@event.UserId}'.");
43 | }
44 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Services/WalletsModuleApi.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Wallets.Application.Wallets.Queries;
4 | using NPay.Modules.Wallets.Shared;
5 | using NPay.Modules.Wallets.Shared.DTO;
6 | using NPay.Shared.Dispatchers;
7 |
8 | namespace NPay.Modules.Wallets.Application.Services;
9 |
10 | internal sealed class WalletsModuleApi : IWalletsModuleApi
11 | {
12 | private readonly IDispatcher _dispatcher;
13 |
14 | public WalletsModuleApi(IDispatcher dispatcher)
15 | {
16 | _dispatcher = dispatcher;
17 | }
18 |
19 | public Task GetWalletAsync(Guid walletId)
20 | => _dispatcher.QueryAsync(new GetWallet { WalletId = walletId });
21 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Commands/AddFunds.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Commands;
3 |
4 | namespace NPay.Modules.Wallets.Application.Wallets.Commands;
5 |
6 | public record AddFunds(Guid WalletId, decimal Amount) : ICommand;
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Commands/AddWallet.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Commands;
3 |
4 | namespace NPay.Modules.Wallets.Application.Wallets.Commands;
5 |
6 | public record AddWallet(Guid OwnerId, string Currency) : ICommand
7 | {
8 | public Guid WalletId { get; init; } = Guid.NewGuid();
9 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Commands/Handlers/AddFundsHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Logging;
4 | using NPay.Modules.Wallets.Core.Wallets.Exceptions;
5 | using NPay.Modules.Wallets.Core.Wallets.Repositories;
6 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
7 | using NPay.Modules.Wallets.Shared.Events;
8 | using NPay.Shared.Commands;
9 | using NPay.Shared.Messaging;
10 | using NPay.Shared.Time;
11 |
12 | namespace NPay.Modules.Wallets.Application.Wallets.Commands.Handlers;
13 |
14 | internal sealed class AddFundsHandler : ICommandHandler
15 | {
16 | private readonly IWalletRepository _walletRepository;
17 | private readonly IClock _clock;
18 | private readonly IMessageBroker _messageBroker;
19 | private readonly ILogger _logger;
20 |
21 | public AddFundsHandler(IWalletRepository walletRepository, IClock clock, IMessageBroker messageBroker,
22 | ILogger logger)
23 |
24 | {
25 | _walletRepository = walletRepository;
26 | _clock = clock;
27 | _messageBroker = messageBroker;
28 | _logger = logger;
29 | }
30 |
31 | public async Task HandleAsync(AddFunds command, CancellationToken cancellationToken = default)
32 | {
33 | var (walletId, amount) = command;
34 | var wallet = await _walletRepository.GetAsync(walletId);
35 | if (wallet is null)
36 | {
37 | throw new WalletNotFoundException(walletId);
38 | }
39 |
40 | var now = _clock.CurrentDate();
41 | var transfer = wallet.AddFunds(new TransferId(), amount, now);
42 | await _walletRepository.UpdateAsync(wallet);
43 | await _messageBroker.PublishAsync(new FundsAdded(walletId, wallet.OwnerId, transfer.Currency,
44 | transfer.Amount), cancellationToken);
45 | _logger.LogInformation($"Added {transfer.Amount} {transfer.Currency} to the wallet: '{wallet.Id}'");
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Commands/Handlers/AddWalletHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Logging;
4 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
5 | using NPay.Modules.Wallets.Core.Wallets.Repositories;
6 | using NPay.Modules.Wallets.Shared.Events;
7 | using NPay.Shared.Commands;
8 | using NPay.Shared.Messaging;
9 | using NPay.Shared.Time;
10 |
11 | namespace NPay.Modules.Wallets.Application.Wallets.Commands.Handlers;
12 |
13 | internal sealed class AddWalletHandler : ICommandHandler
14 | {
15 | private readonly IWalletRepository _walletRepository;
16 | private readonly IClock _clock;
17 | private readonly IMessageBroker _messageBroker;
18 | private readonly ILogger _logger;
19 |
20 | public AddWalletHandler(IWalletRepository walletRepository, IClock clock, IMessageBroker messageBroker,
21 | ILogger logger)
22 | {
23 | _walletRepository = walletRepository;
24 | _clock = clock;
25 | _messageBroker = messageBroker;
26 | _logger = logger;
27 | }
28 |
29 | public async Task HandleAsync(AddWallet command, CancellationToken cancellationToken = default)
30 | {
31 | var now = _clock.CurrentDate();
32 | var wallet = Wallet.Create(command.WalletId, command.OwnerId, command.Currency, now);
33 | await _walletRepository.AddAsync(wallet);
34 | await _messageBroker.PublishAsync(new WalletAdded(wallet.Id, wallet.OwnerId, wallet.Currency),
35 | cancellationToken);
36 | _logger.LogInformation($"Added the wallet with ID: '{wallet.Id}' for an owner: '{wallet.OwnerId}'.");
37 | }
38 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Commands/Handlers/TransferFundsHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Logging;
4 | using NPay.Modules.Wallets.Core.Wallets.Exceptions;
5 | using NPay.Modules.Wallets.Core.Wallets.Repositories;
6 | using NPay.Modules.Wallets.Shared.Events;
7 | using NPay.Shared.Commands;
8 | using NPay.Shared.Messaging;
9 | using NPay.Shared.Time;
10 |
11 | namespace NPay.Modules.Wallets.Application.Wallets.Commands.Handlers;
12 |
13 | internal sealed class TransferFundsHandler : ICommandHandler
14 | {
15 | private readonly IWalletRepository _walletRepository;
16 | private readonly IClock _clock;
17 | private readonly IMessageBroker _messageBroker;
18 | private readonly ILogger _logger;
19 |
20 | public TransferFundsHandler(IWalletRepository walletRepository, IClock clock, IMessageBroker messageBroker,
21 | ILogger logger)
22 | {
23 | _walletRepository = walletRepository;
24 | _clock = clock;
25 | _messageBroker = messageBroker;
26 | _logger = logger;
27 | }
28 |
29 | public async Task HandleAsync(TransferFunds command, CancellationToken cancellationToken = default)
30 | {
31 | var (fromWalletId, toWalletId, amount) = command;
32 | var fromWallet = await _walletRepository.GetAsync(fromWalletId);
33 | if (fromWallet is null)
34 | {
35 | throw new WalletNotFoundException(fromWalletId);
36 | }
37 |
38 | var toWallet = await _walletRepository.GetAsync(toWalletId);
39 | if (toWallet is null)
40 | {
41 | throw new WalletNotFoundException(toWalletId);
42 | }
43 |
44 | var now = _clock.CurrentDate();
45 | var currency = fromWallet.Currency;
46 | fromWallet.TransferFunds(toWallet, amount, now);
47 | await _walletRepository.UpdateAsync(fromWallet);
48 | await _walletRepository.UpdateAsync(toWallet);
49 | await _messageBroker.PublishAsync(new FundsTransferred(fromWalletId, toWalletId, amount, currency),
50 | cancellationToken);
51 | _logger.LogInformation($"Transferred {amount} {currency} from: '{fromWallet.Id}' to: '{toWallet.Id}'.");
52 | }
53 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Commands/TransferFunds.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Commands;
3 |
4 | namespace NPay.Modules.Wallets.Application.Wallets.Commands;
5 |
6 | public record TransferFunds(Guid FromWalletId, Guid ToWalletId, decimal Amount) : ICommand;
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/ExternalEvents/Handlers/UserVerifiedHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Logging;
4 | using NPay.Modules.Users.Shared.Events;
5 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
6 | using NPay.Modules.Wallets.Core.Wallets.Repositories;
7 | using NPay.Modules.Wallets.Core.Wallets.Services;
8 | using NPay.Modules.Wallets.Shared.Events;
9 | using NPay.Shared.Events;
10 | using NPay.Shared.Messaging;
11 | using NPay.Shared.Time;
12 |
13 | namespace NPay.Modules.Wallets.Application.Wallets.ExternalEvents.Handlers;
14 |
15 | internal sealed class UserVerifiedHandler : IEventHandler
16 | {
17 | private readonly IWalletRepository _walletRepository;
18 | private readonly ICurrencyResolver _currencyResolver;
19 | private readonly IClock _clock;
20 | private readonly IMessageBroker _messageBroker;
21 | private readonly ILogger _logger;
22 |
23 | public UserVerifiedHandler(IWalletRepository walletRepository, ICurrencyResolver currencyResolver,
24 | IClock clock, IMessageBroker messageBroker, ILogger logger)
25 | {
26 | _walletRepository = walletRepository;
27 | _currencyResolver = currencyResolver;
28 | _clock = clock;
29 | _messageBroker = messageBroker;
30 | _logger = logger;
31 | }
32 |
33 | public async Task HandleAsync(UserVerified @event, CancellationToken cancellationToken = default)
34 | {
35 | var now = _clock.CurrentDate();
36 | var currency = _currencyResolver.Resolve(@event.Nationality);
37 | var wallet = Wallet.Create(@event.UserId, currency, now);
38 | await _walletRepository.AddAsync(wallet);
39 | await _messageBroker.PublishAsync(new WalletAdded(wallet.Id, wallet.OwnerId, wallet.Currency),
40 | cancellationToken);
41 | _logger.LogInformation($"Added the wallet with ID: '{wallet.Id}' for an owner: '{wallet.OwnerId}'.");
42 | }
43 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Queries/GetWallet.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Modules.Wallets.Shared.DTO;
3 | using NPay.Shared.Queries;
4 |
5 | namespace NPay.Modules.Wallets.Application.Wallets.Queries;
6 |
7 | public class GetWallet : IQuery
8 | {
9 | public Guid WalletId { get; set; }
10 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Queries/Handlers/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
3 | using NPay.Modules.Wallets.Core.Wallets.Entities;
4 | using NPay.Modules.Wallets.Shared.DTO;
5 |
6 | namespace NPay.Modules.Wallets.Application.Wallets.Queries.Handlers;
7 |
8 | internal static class Extensions
9 | {
10 | public static WalletDto AsDto(this Wallet wallet)
11 | => new()
12 | {
13 | WalletId = wallet.Id,
14 | OwnerId = wallet.OwnerId,
15 | Currency = wallet.Currency,
16 | CreatedAt = wallet.CreatedAt,
17 | Amount = wallet.CurrentAmount(),
18 | Transfers = wallet.Transfers.Select(x => x.AsDto())
19 | .OrderByDescending(x => x.CreatedAt)
20 | .ToList()
21 | };
22 |
23 | public static TransferDto AsDto(this Transfer transfer)
24 | => new()
25 | {
26 | TransferId = transfer.Id,
27 | ReferenceId = transfer.ReferenceId,
28 | WalletId = transfer.WalletId,
29 | Amount = transfer.Amount,
30 | Currency = transfer.Currency,
31 | Direction = transfer.Direction.ToString().ToLowerInvariant(),
32 | CreatedAt = transfer.CreatedAt
33 | };
34 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Queries/Handlers/GetWalletHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Wallets.Application.Wallets.Storage;
4 | using NPay.Modules.Wallets.Shared.DTO;
5 | using NPay.Shared.Queries;
6 |
7 | namespace NPay.Modules.Wallets.Application.Wallets.Queries.Handlers;
8 |
9 | internal sealed class GetWalletHandler : IQueryHandler
10 | {
11 | private readonly IWalletStorage _storage;
12 |
13 | public GetWalletHandler(IWalletStorage storage)
14 | {
15 | _storage = storage;
16 | }
17 |
18 | public async Task HandleAsync(GetWallet query, CancellationToken cancellationToken = default)
19 | {
20 | var wallet = await _storage.FindAsync(x => x.Id == query.WalletId);
21 |
22 | return wallet?.AsDto();
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Storage/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq.Expressions;
4 |
5 | namespace NPay.Modules.Wallets.Application.Wallets.Storage;
6 |
7 | internal static class Extensions
8 | {
9 | public static Expression> And(this Expression> a, Expression> b)
10 | {
11 | var firstParam = a.Parameters[0];
12 | var visitor = new SubstExpressionVisitor
13 | {
14 | Dictionary =
15 | {
16 | [b.Parameters[0]] = firstParam
17 | }
18 | };
19 |
20 | var body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
21 | return Expression.Lambda>(body, firstParam);
22 | }
23 |
24 | public static Expression> Or(this Expression> a, Expression> b)
25 | {
26 | var firstParam = a.Parameters[0];
27 | var visitor = new SubstExpressionVisitor
28 | {
29 | Dictionary =
30 | {
31 | [b.Parameters[0]] = firstParam
32 | }
33 | };
34 |
35 | var body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
36 | return Expression.Lambda>(body, firstParam);
37 | }
38 |
39 |
40 | private class SubstExpressionVisitor : ExpressionVisitor
41 | {
42 | public readonly Dictionary Dictionary = new();
43 |
44 | protected override Expression VisitParameter(ParameterExpression node)
45 | => Dictionary.TryGetValue(node, out var newValue) ? newValue : node;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Application/Wallets/Storage/IWalletStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 | using System.Threading.Tasks;
4 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
5 |
6 | namespace NPay.Modules.Wallets.Application.Wallets.Storage;
7 |
8 | internal interface IWalletStorage
9 | {
10 | Task FindAsync(Expression> expression);
11 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using NPay.Modules.Wallets.Core.Wallets.Services;
4 |
5 | [assembly: InternalsVisibleTo("NPay.Modules.Wallets.Application")]
6 | [assembly: InternalsVisibleTo("NPay.Modules.Wallets.Infrastructure")]
7 | namespace NPay.Modules.Wallets.Core;
8 |
9 | public static class Extensions
10 | {
11 | public static IServiceCollection AddCoreLayer(this IServiceCollection services)
12 | {
13 | services.AddSingleton();
14 |
15 | return services;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/NPay.Modules.Wallets.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Owners/Aggregates/Owner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Modules.Wallets.Core.Owners.ValueObjects;
3 | using NPay.Modules.Wallets.Core.SharedKernel;
4 |
5 | namespace NPay.Modules.Wallets.Core.Owners.Aggregates;
6 |
7 | internal class Owner : AggregateRoot
8 | {
9 | public FullName FullName { get; private set; }
10 | public Nationality Nationality { get; private set; }
11 | public DateTime CreatedAt { get; private set; }
12 | public DateTime? VerifiedAt { get; private set; }
13 | public bool IsVerified => VerifiedAt.HasValue;
14 |
15 | protected Owner()
16 | {
17 | }
18 |
19 | public Owner(OwnerId id, FullName fullName, Nationality nationality, DateTime createdAt)
20 | {
21 | Id = id;
22 | FullName = fullName;
23 | Nationality = nationality;
24 | CreatedAt = createdAt;
25 | }
26 |
27 | public void Verify(DateTime verifiedAt)
28 | {
29 | if (IsVerified)
30 | {
31 | return;
32 | }
33 |
34 | VerifiedAt = verifiedAt;
35 | IncrementVersion();
36 | }
37 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Owners/Exceptions/InvalidFullNameException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Core.Owners.Exceptions;
4 |
5 | internal sealed class InvalidFullNameException : NPayException
6 | {
7 | public string FullName { get; }
8 |
9 | public InvalidFullNameException(string fullName) : base($"Full name: '{fullName}' is invalid.")
10 | {
11 | FullName = fullName;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Owners/Exceptions/InvalidNationalityException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Core.Owners.Exceptions;
4 |
5 | internal sealed class InvalidNationalityException : NPayException
6 | {
7 | public string Nationality { get; }
8 |
9 | public InvalidNationalityException(string nationality)
10 | : base($"Nationality: '{nationality}' is invalid.")
11 | {
12 | Nationality = nationality;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Owners/Exceptions/OwnerNotFoundException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Exceptions;
3 |
4 | namespace NPay.Modules.Wallets.Core.Owners.Exceptions;
5 |
6 | internal sealed class OwnerNotFoundException : NPayException
7 | {
8 | public Guid OwnerId { get; }
9 |
10 | public OwnerNotFoundException(Guid ownerId) : base($"Owner with ID: '{ownerId}' was not found.")
11 | {
12 | OwnerId = ownerId;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Owners/Exceptions/UnsupportedNationalityException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Core.Owners.Exceptions;
4 |
5 | internal sealed class UnsupportedNationalityException : NPayException
6 | {
7 | public string Nationality { get; }
8 |
9 | public UnsupportedNationalityException(string nationality)
10 | : base($"Nationality: '{nationality}' is unsupported.")
11 | {
12 | Nationality = nationality;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Owners/Repositories/IOwnerRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using NPay.Modules.Wallets.Core.Owners.Aggregates;
3 | using NPay.Modules.Wallets.Core.SharedKernel;
4 |
5 | namespace NPay.Modules.Wallets.Core.Owners.Repositories;
6 |
7 | internal interface IOwnerRepository
8 | {
9 | Task GetAsync(OwnerId id);
10 | Task AddAsync(Owner owner);
11 | Task UpdateAsync(Owner owner);
12 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Owners/ValueObjects/FullName.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Modules.Wallets.Core.Owners.Exceptions;
3 |
4 | namespace NPay.Modules.Wallets.Core.Owners.ValueObjects;
5 |
6 | internal sealed class FullName : IEquatable
7 | {
8 | public string Value { get; }
9 |
10 | public FullName(string value)
11 | {
12 | if (string.IsNullOrWhiteSpace(value) || value.Length is > 100 or < 2)
13 | {
14 | throw new InvalidFullNameException(value);
15 | }
16 |
17 | Value = value;
18 | }
19 |
20 | public static implicit operator FullName(string value) => value is null ? null : new FullName(value);
21 |
22 | public static implicit operator string(FullName value) => value?.Value;
23 |
24 | public bool Equals(FullName other)
25 | {
26 | if (ReferenceEquals(null, other)) return false;
27 | if (ReferenceEquals(this, other)) return true;
28 | return Value == other.Value;
29 | }
30 |
31 | public override bool Equals(object obj)
32 | {
33 | if (ReferenceEquals(null, obj)) return false;
34 | if (ReferenceEquals(this, obj)) return true;
35 | return obj.GetType() == GetType() && Equals((FullName)obj);
36 | }
37 |
38 | public override int GetHashCode() => Value is not null ? Value.GetHashCode() : 0;
39 |
40 | public override string ToString() => Value;
41 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Owners/ValueObjects/Nationality.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using NPay.Modules.Wallets.Core.Owners.Exceptions;
4 |
5 | namespace NPay.Modules.Wallets.Core.Owners.ValueObjects;
6 |
7 | internal sealed class Nationality : IEquatable
8 | {
9 | private static readonly HashSet AllowedValues = new()
10 | {
11 | "PL", "DE", "FR", "ES", "GB"
12 | };
13 |
14 | public string Value { get; }
15 |
16 | public Nationality(string value)
17 | {
18 | if (string.IsNullOrWhiteSpace(value) || value.Length != 2)
19 | {
20 | throw new InvalidNationalityException(value);
21 | }
22 |
23 | value = value.ToUpperInvariant();
24 | if (!AllowedValues.Contains(value))
25 | {
26 | throw new UnsupportedNationalityException(value);
27 | }
28 |
29 | Value = value;
30 | }
31 |
32 | public static implicit operator Nationality(string value) => value is null ? null : new Nationality(value);
33 |
34 | public static implicit operator string(Nationality value) => value?.Value;
35 |
36 | public bool Equals(Nationality other)
37 | {
38 | if (ReferenceEquals(null, other)) return false;
39 | if (ReferenceEquals(this, other)) return true;
40 | return Value == other.Value;
41 | }
42 |
43 | public override bool Equals(object obj)
44 | {
45 | if (ReferenceEquals(null, obj)) return false;
46 | if (ReferenceEquals(this, obj)) return true;
47 | return obj.GetType() == GetType() && Equals((Nationality)obj);
48 | }
49 |
50 | public override int GetHashCode() => Value is not null ? Value.GetHashCode() : 0;
51 |
52 | public override string ToString() => Value;
53 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/SharedKernel/AggregateRoot.cs:
--------------------------------------------------------------------------------
1 | namespace NPay.Modules.Wallets.Core.SharedKernel;
2 |
3 | public abstract class AggregateRoot
4 | {
5 | private bool _versionIncremented;
6 | public T Id { get; protected set; }
7 | public int Version { get; protected set; } = 1;
8 |
9 | protected void IncrementVersion()
10 | {
11 | if (_versionIncremented)
12 | {
13 | return;
14 | }
15 |
16 | Version++;
17 | _versionIncremented = true;
18 | }
19 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/SharedKernel/OwnerId.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NPay.Modules.Wallets.Core.SharedKernel;
4 |
5 | internal sealed class OwnerId : IEquatable
6 | {
7 | public Guid Value { get; }
8 |
9 | public OwnerId(Guid value)
10 | {
11 | Value = value;
12 | }
13 |
14 | public bool Equals(OwnerId other)
15 | {
16 | if (ReferenceEquals(null, other)) return false;
17 | return ReferenceEquals(this, other) || Value.Equals(other.Value);
18 | }
19 |
20 | public override bool Equals(object obj)
21 | {
22 | if (ReferenceEquals(null, obj)) return false;
23 | if (ReferenceEquals(this, obj)) return true;
24 | return obj.GetType() == GetType() && Equals((OwnerId)obj);
25 | }
26 |
27 | public override int GetHashCode() => Value.GetHashCode();
28 |
29 | public override string ToString() => Value.ToString();
30 |
31 | public static implicit operator OwnerId(Guid value) => new(value);
32 |
33 | public static implicit operator Guid(OwnerId value) => value.Value;
34 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Aggregates/Wallet.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using NPay.Modules.Wallets.Core.SharedKernel;
5 | using NPay.Modules.Wallets.Core.Wallets.Entities;
6 | using NPay.Modules.Wallets.Core.Wallets.Exceptions;
7 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
8 |
9 | namespace NPay.Modules.Wallets.Core.Wallets.Aggregates;
10 |
11 | internal class Wallet : AggregateRoot
12 | {
13 | private HashSet _transfers = new();
14 |
15 | public OwnerId OwnerId { get; private set; }
16 | public Currency Currency { get; private set; }
17 |
18 | public IEnumerable Transfers
19 | {
20 | get => _transfers;
21 | set => _transfers = new HashSet(value);
22 | }
23 |
24 | public DateTime CreatedAt { get; private set; }
25 |
26 | private Wallet()
27 | {
28 | }
29 |
30 | private Wallet(WalletId id, OwnerId ownerId, Currency currency, DateTime createdAt)
31 | {
32 | Id = id;
33 | OwnerId = ownerId;
34 | Currency = currency;
35 | CreatedAt = createdAt;
36 | }
37 |
38 | public static Wallet Create(OwnerId ownerId, Currency currency, DateTime createdAt)
39 | => Create(new WalletId(), ownerId, currency, createdAt);
40 |
41 | public static Wallet Create(WalletId id, OwnerId ownerId, Currency currency, DateTime createdAt)
42 | => new Wallet(id, ownerId, currency, createdAt);
43 |
44 | public IReadOnlyCollection TransferFunds(Wallet receiver, Amount amount, DateTime createdAt)
45 | {
46 | var outTransferId = new TransferId();
47 | var inTransferId = new TransferId();
48 |
49 | var outTransfer = DeductFunds(outTransferId, amount, createdAt, inTransferId);
50 | var inTransfer = receiver.AddFunds(inTransferId, amount, createdAt, outTransferId);
51 |
52 | return new[] { outTransfer, inTransfer };
53 | }
54 |
55 | public Transfer AddFunds(TransferId transferId, Amount amount, DateTime createdAt,
56 | TransferId referenceId = null)
57 | {
58 | if (amount <= 0)
59 | {
60 | throw new InvalidTransferAmountException(amount);
61 | }
62 |
63 | var transfer = Transfer.Incoming(transferId, Id, Currency, amount, createdAt, referenceId);
64 | _transfers.Add(transfer);
65 | IncrementVersion();
66 |
67 | return transfer;
68 | }
69 |
70 | public Transfer DeductFunds(TransferId transferId, Amount amount, DateTime createdAt,
71 | TransferId referenceId = null)
72 | {
73 | if (amount <= 0)
74 | {
75 | throw new InvalidTransferAmountException(amount);
76 | }
77 |
78 | if (CurrentAmount() < amount)
79 | {
80 | throw new InsufficientWalletFundsException(Id);
81 | }
82 |
83 | var transfer = Transfer.Outgoing(transferId, Id, Currency, amount, createdAt, referenceId);
84 | _transfers.Add(transfer);
85 | IncrementVersion();
86 |
87 | return transfer;
88 | }
89 |
90 | public Amount CurrentAmount() => SumIncomingAmount() - SumOutgoingAmount();
91 |
92 | private Amount SumIncomingAmount()
93 | => _transfers.Where(x => x.Direction == Transfer.TransferDirection.In).Sum(x => x.Amount);
94 |
95 | private Amount SumOutgoingAmount()
96 | => _transfers.Where(x => x.Direction == Transfer.TransferDirection.Out).Sum(x => x.Amount);
97 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Entities/Transfer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
3 |
4 | namespace NPay.Modules.Wallets.Core.Wallets.Entities;
5 |
6 | internal class Transfer
7 | {
8 | public TransferId Id { get; private set; }
9 | public TransferId ReferenceId { get; private set; }
10 | public WalletId WalletId { get; private set; }
11 | public Currency Currency { get; private set; }
12 | public Amount Amount { get; private set; }
13 | public TransferDirection Direction { get; private set; }
14 | public DateTime CreatedAt { get; private set; }
15 |
16 | private Transfer()
17 | {
18 | }
19 |
20 | private Transfer(TransferId id, WalletId walletId, Currency currency, Amount amount,
21 | TransferDirection direction, DateTime createdAt, TransferId referenceId = null)
22 | {
23 | Id = id;
24 | WalletId = walletId;
25 | Currency = currency;
26 | Amount = amount;
27 | Direction = direction;
28 | CreatedAt = createdAt;
29 | ReferenceId = referenceId;
30 | }
31 |
32 | public static Transfer Incoming(TransferId id, WalletId walletId, Currency currency, Amount amount,
33 | DateTime createdAt, TransferId referenceId = null)
34 | => new(id, walletId, currency, amount, TransferDirection.In, createdAt, referenceId);
35 |
36 | public static Transfer Outgoing(TransferId id, WalletId walletId, Currency currency, Amount amount,
37 | DateTime createdAt, TransferId referenceId = null)
38 | => new(id, walletId, currency, amount, TransferDirection.Out, createdAt, referenceId);
39 |
40 | internal enum TransferDirection
41 | {
42 | In,
43 | Out
44 | }
45 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Exceptions/InsufficientWalletFundsException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Exceptions;
3 |
4 | namespace NPay.Modules.Wallets.Core.Wallets.Exceptions;
5 |
6 | internal sealed class InsufficientWalletFundsException : NPayException
7 | {
8 | public Guid WalletId { get; }
9 |
10 | public InsufficientWalletFundsException(Guid walletId)
11 | : base($"Insufficient funds for wallet with ID: '{walletId}'.")
12 | {
13 | WalletId = walletId;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Exceptions/InvalidAmountException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Core.Wallets.Exceptions;
4 |
5 | internal sealed class InvalidAmountException : NPayException
6 | {
7 | public decimal Amount { get; }
8 |
9 | public InvalidAmountException(decimal amount) : base($"Amount: '{amount}' is invalid.")
10 | {
11 | Amount = amount;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Exceptions/InvalidCurrencyException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Core.Wallets.Exceptions;
4 |
5 | internal sealed class InvalidCurrencyException : NPayException
6 | {
7 | public string Currency { get; }
8 |
9 | public InvalidCurrencyException(string currency) : base($"Currency: '{currency}' is invalid.")
10 | {
11 | Currency = currency;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Exceptions/InvalidTransferAmountException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Core.Wallets.Exceptions;
4 |
5 | internal sealed class InvalidTransferAmountException : NPayException
6 | {
7 | public decimal Amount { get; }
8 |
9 | public InvalidTransferAmountException(decimal amount) : base($"Transfer has invalid amount: '{amount}'.")
10 | {
11 | Amount = amount;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Exceptions/UnsupportedCurrencyException.cs:
--------------------------------------------------------------------------------
1 | using NPay.Shared.Exceptions;
2 |
3 | namespace NPay.Modules.Wallets.Core.Wallets.Exceptions;
4 |
5 | internal sealed class UnsupportedCurrencyException : NPayException
6 | {
7 | public string Currency { get; }
8 |
9 | public UnsupportedCurrencyException(string currency) : base($"Currency: '{currency}' is unsupported.")
10 | {
11 | Currency = currency;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Exceptions/WalletNotFoundException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Exceptions;
3 |
4 | namespace NPay.Modules.Wallets.Core.Wallets.Exceptions;
5 |
6 | internal sealed class WalletNotFoundException : NPayException
7 | {
8 | public Guid OwnerId { get; }
9 | public string Currency { get; }
10 | public Guid WalletId { get; }
11 |
12 | public WalletNotFoundException(Guid walletId) : base($"Wallet with ID: '{walletId}' was not found.")
13 | {
14 | WalletId = walletId;
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Repositories/IWalletRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using NPay.Modules.Wallets.Core.SharedKernel;
3 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
4 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
5 |
6 | namespace NPay.Modules.Wallets.Core.Wallets.Repositories;
7 |
8 | internal interface IWalletRepository
9 | {
10 | Task GetAsync(WalletId id);
11 | Task GetAsync(OwnerId ownerId, Currency currency);
12 | Task AddAsync(Wallet wallet);
13 | Task UpdateAsync(Wallet wallet);
14 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Services/CurrencyResolver.cs:
--------------------------------------------------------------------------------
1 | using NPay.Modules.Wallets.Core.Owners.ValueObjects;
2 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
3 |
4 | namespace NPay.Modules.Wallets.Core.Wallets.Services;
5 |
6 | internal sealed class CurrencyResolver : ICurrencyResolver
7 | {
8 | public Currency Resolve(Nationality nationality)
9 | => nationality.Value switch
10 | {
11 | "PL" => "PLN",
12 | "DE" => "EUR",
13 | "FR" => "EUR",
14 | "ES" => "EUR",
15 | "GB" => "GBP",
16 | _ => "EUR"
17 | };
18 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/Services/ICurrencyResolver.cs:
--------------------------------------------------------------------------------
1 | using NPay.Modules.Wallets.Core.Owners.ValueObjects;
2 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
3 |
4 | namespace NPay.Modules.Wallets.Core.Wallets.Services;
5 |
6 | internal interface ICurrencyResolver
7 | {
8 | Currency Resolve(Nationality nationality);
9 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/ValueObjects/Amount.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using NPay.Modules.Wallets.Core.Wallets.Exceptions;
4 |
5 | namespace NPay.Modules.Wallets.Core.Wallets.ValueObjects;
6 |
7 | internal sealed class Amount : IEquatable
8 | {
9 | public decimal Value { get; }
10 |
11 | public Amount(decimal value)
12 | {
13 | if (value is < 0 or > 1000000)
14 | {
15 | throw new InvalidAmountException(value);
16 | }
17 |
18 | Value = value;
19 | }
20 |
21 | public static Amount Zero => new(0);
22 |
23 | public static implicit operator Amount(decimal value) => new(value);
24 |
25 | public static implicit operator decimal(Amount value) => value.Value;
26 |
27 | public static bool operator ==(Amount a, Amount b)
28 | {
29 | if (ReferenceEquals(a, b))
30 | {
31 | return true;
32 | }
33 |
34 | if (a is not null && b is not null)
35 | {
36 | return a.Value.Equals(b.Value);
37 | }
38 |
39 | return false;
40 | }
41 |
42 | public static bool operator !=(Amount a, Amount b) => !(a == b);
43 |
44 | public static bool operator >(Amount a, Amount b) => a.Value > b.Value;
45 |
46 | public static bool operator <(Amount a, Amount b) => a.Value < b.Value;
47 |
48 | public static bool operator >=(Amount a, Amount b) => a.Value >= b.Value;
49 |
50 | public static bool operator <=(Amount a, Amount b) => a.Value <= b.Value;
51 |
52 | public static Amount operator +(Amount a, Amount b) => a.Value + b.Value;
53 |
54 | public static Amount operator -(Amount a, Amount b) => a.Value - b.Value;
55 |
56 | public bool Equals(Amount other)
57 | {
58 | if (ReferenceEquals(null, other)) return false;
59 | if (ReferenceEquals(this, other)) return true;
60 | return Value == other.Value;
61 | }
62 |
63 | public override bool Equals(object obj)
64 | {
65 | if (ReferenceEquals(null, obj)) return false;
66 | if (ReferenceEquals(this, obj)) return true;
67 | return obj.GetType() == GetType() && Equals((Amount)obj);
68 | }
69 |
70 | public override int GetHashCode() => Value.GetHashCode();
71 |
72 | public override string ToString() => Value.ToString(CultureInfo.InvariantCulture);
73 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/ValueObjects/Currency.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using NPay.Modules.Wallets.Core.Wallets.Exceptions;
4 |
5 | namespace NPay.Modules.Wallets.Core.Wallets.ValueObjects;
6 |
7 | internal sealed class Currency : IEquatable
8 | {
9 | private static readonly HashSet AllowedValues = new()
10 | {
11 | "PLN", "EUR", "GBP"
12 | };
13 |
14 | public string Value { get; }
15 |
16 | public Currency(string value)
17 | {
18 | if (string.IsNullOrWhiteSpace(value) || value.Length != 3)
19 | {
20 | throw new InvalidCurrencyException(value);
21 | }
22 |
23 | value = value.ToUpperInvariant();
24 | if (!AllowedValues.Contains(value))
25 | {
26 | throw new UnsupportedCurrencyException(value);
27 | }
28 |
29 | Value = value;
30 | }
31 |
32 | public static implicit operator Currency(string value) => new(value);
33 |
34 | public static implicit operator string(Currency value) => value.Value;
35 |
36 | public static bool operator ==(Currency a, Currency b)
37 | {
38 | if (ReferenceEquals(a, b))
39 | {
40 | return true;
41 | }
42 |
43 | if (a is not null && b is not null)
44 | {
45 | return a.Value.Equals(b.Value);
46 | }
47 |
48 | return false;
49 | }
50 |
51 | public static bool operator !=(Currency a, Currency b) => !(a == b);
52 |
53 | public bool Equals(Currency other)
54 | {
55 | if (ReferenceEquals(null, other)) return false;
56 | if (ReferenceEquals(this, other)) return true;
57 | return Value == other.Value;
58 | }
59 |
60 | public override bool Equals(object obj)
61 | {
62 | if (ReferenceEquals(null, obj)) return false;
63 | if (ReferenceEquals(this, obj)) return true;
64 | return obj.GetType() == GetType() && Equals((Currency)obj);
65 | }
66 |
67 | public override int GetHashCode() => Value is not null ? Value.GetHashCode() : 0;
68 |
69 | public override string ToString() => Value;
70 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/ValueObjects/TransferId.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NPay.Modules.Wallets.Core.Wallets.ValueObjects;
4 |
5 | internal sealed class TransferId : IEquatable
6 | {
7 | public Guid Value { get; }
8 |
9 | public TransferId() : this(Guid.NewGuid())
10 | {
11 | }
12 |
13 | public TransferId(Guid value)
14 | {
15 | Value = value;
16 | }
17 |
18 | public bool Equals(TransferId other)
19 | {
20 | if (ReferenceEquals(null, other)) return false;
21 | return ReferenceEquals(this, other) || Value.Equals(other.Value);
22 | }
23 |
24 | public override bool Equals(object obj)
25 | {
26 | if (ReferenceEquals(null, obj)) return false;
27 | if (ReferenceEquals(this, obj)) return true;
28 | return obj.GetType() == GetType() && Equals((TransferId)obj);
29 | }
30 |
31 | public override int GetHashCode() => Value.GetHashCode();
32 |
33 | public override string ToString() => Value.ToString();
34 |
35 | public static implicit operator TransferId(Guid value) => new(value);
36 |
37 | public static implicit operator Guid(TransferId value) => value.Value;
38 |
39 | public static implicit operator Guid?(TransferId value) => value?.Value;
40 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Core/Wallets/ValueObjects/WalletId.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NPay.Modules.Wallets.Core.Wallets.ValueObjects;
4 |
5 | internal sealed class WalletId : IEquatable
6 | {
7 | public Guid Value { get; }
8 |
9 | public WalletId() : this(Guid.NewGuid())
10 | {
11 | }
12 |
13 | public WalletId(Guid value)
14 | {
15 | Value = value;
16 | }
17 |
18 | public bool Equals(WalletId other)
19 | {
20 | if (ReferenceEquals(null, other)) return false;
21 | return ReferenceEquals(this, other) || Value.Equals(other.Value);
22 | }
23 |
24 | public override bool Equals(object obj)
25 | {
26 | if (ReferenceEquals(null, obj)) return false;
27 | if (ReferenceEquals(this, obj)) return true;
28 | return obj.GetType() == GetType() && Equals((WalletId)obj);
29 | }
30 |
31 | public override int GetHashCode() => Value.GetHashCode();
32 |
33 | public override string ToString() => Value.ToString();
34 |
35 | public static implicit operator WalletId(Guid value) => new(value);
36 |
37 | public static implicit operator Guid(WalletId value) => value.Value;
38 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/Configurations/OwnerConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
3 | using NPay.Modules.Wallets.Core.Owners.Aggregates;
4 | using NPay.Modules.Wallets.Core.Owners.ValueObjects;
5 | using NPay.Modules.Wallets.Core.SharedKernel;
6 |
7 | namespace NPay.Modules.Wallets.Infrastructure.DAL.Configurations;
8 |
9 | internal class OwnerConfiguration : IEntityTypeConfiguration
10 | {
11 | public void Configure(EntityTypeBuilder builder)
12 | {
13 | builder.ToTable("Owners");
14 |
15 | builder.Property(x => x.Id)
16 | .HasConversion(x => x.Value, x => new OwnerId(x));
17 |
18 | builder.Property(x => x.FullName)
19 | .IsRequired()
20 | .HasConversion(x => x.Value, x => new FullName(x));
21 |
22 | builder.Property(x => x.Nationality)
23 | .IsRequired()
24 | .HasConversion(x => x.Value, x => new Nationality(x));
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/Configurations/TransferConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
3 | using NPay.Modules.Wallets.Core.Wallets.Entities;
4 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
5 |
6 | namespace NPay.Modules.Wallets.Infrastructure.DAL.Configurations;
7 |
8 | internal class TransferConfiguration : IEntityTypeConfiguration
9 | {
10 | public void Configure(EntityTypeBuilder builder)
11 | {
12 | builder.ToTable("Transfers");
13 |
14 | builder.Property(x => x.Id)
15 | .HasConversion(x => x.Value, x => new TransferId(x));
16 |
17 | builder.Property(x => x.ReferenceId)
18 | .HasConversion(x => x.Value, x => new TransferId(x));
19 |
20 | builder.Property(x => x.WalletId)
21 | .IsRequired()
22 | .HasConversion(x => x.Value, x => new WalletId(x));
23 |
24 | builder.Property(x => x.Currency)
25 | .IsRequired()
26 | .HasConversion(x => x.Value, x => new Currency(x));
27 |
28 | builder.Property(x => x.Amount)
29 | .IsRequired()
30 | .HasConversion(x => x.Value, x => new Amount(x));
31 | }
32 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/Configurations/WalletConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
3 | using NPay.Modules.Wallets.Core.Owners.Aggregates;
4 | using NPay.Modules.Wallets.Core.SharedKernel;
5 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
6 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
7 |
8 | namespace NPay.Modules.Wallets.Infrastructure.DAL.Configurations;
9 |
10 | internal class WalletConfiguration : IEntityTypeConfiguration
11 | {
12 | public void Configure(EntityTypeBuilder builder)
13 | {
14 | builder.Property(x => x.Version).IsConcurrencyToken();
15 | builder.HasOne().WithMany().HasForeignKey(x => x.OwnerId);
16 |
17 | builder.Property(x => x.Id)
18 | .HasConversion(x => x.Value, x => new WalletId(x));
19 |
20 | builder.Property(x => x.OwnerId)
21 | .IsRequired()
22 | .HasConversion(x => x.Value, x => new OwnerId(x));
23 |
24 | builder.Property(x => x.Currency)
25 | .IsRequired()
26 | .HasConversion(x => x.Value, x => new Currency(x));
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/Migrations/20211020191405_Wallets_Init.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Migrations;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 | using NPay.Modules.Wallets.Infrastructure.DAL;
8 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
9 |
10 | namespace NPay.Modules.Wallets.Infrastructure.DAL.Migrations
11 | {
12 | [DbContext(typeof(WalletsDbContext))]
13 | [Migration("20211020191405_Wallets_Init")]
14 | partial class Wallets_Init
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasDefaultSchema("wallets")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 63)
22 | .HasAnnotation("ProductVersion", "5.0.11")
23 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
24 |
25 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Owners.Aggregates.Owner", b =>
26 | {
27 | b.Property("Id")
28 | .HasColumnType("uuid");
29 |
30 | b.Property("CreatedAt")
31 | .HasColumnType("timestamp without time zone");
32 |
33 | b.Property("FullName")
34 | .IsRequired()
35 | .HasColumnType("text");
36 |
37 | b.Property("Nationality")
38 | .IsRequired()
39 | .HasColumnType("text");
40 |
41 | b.Property("VerifiedAt")
42 | .HasColumnType("timestamp without time zone");
43 |
44 | b.Property("Version")
45 | .HasColumnType("integer");
46 |
47 | b.HasKey("Id");
48 |
49 | b.ToTable("Owners");
50 | });
51 |
52 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Aggregates.Wallet", b =>
53 | {
54 | b.Property("Id")
55 | .HasColumnType("uuid");
56 |
57 | b.Property("CreatedAt")
58 | .HasColumnType("timestamp without time zone");
59 |
60 | b.Property("Currency")
61 | .IsRequired()
62 | .HasColumnType("text");
63 |
64 | b.Property("OwnerId")
65 | .HasColumnType("uuid");
66 |
67 | b.Property("Version")
68 | .IsConcurrencyToken()
69 | .HasColumnType("integer");
70 |
71 | b.HasKey("Id");
72 |
73 | b.HasIndex("OwnerId");
74 |
75 | b.ToTable("Wallets");
76 | });
77 |
78 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Entities.Transfer", b =>
79 | {
80 | b.Property("Id")
81 | .HasColumnType("uuid");
82 |
83 | b.Property("Amount")
84 | .HasColumnType("numeric");
85 |
86 | b.Property("CreatedAt")
87 | .HasColumnType("timestamp without time zone");
88 |
89 | b.Property("Currency")
90 | .IsRequired()
91 | .HasColumnType("text");
92 |
93 | b.Property("Direction")
94 | .HasColumnType("integer");
95 |
96 | b.Property("ReferenceId")
97 | .HasColumnType("uuid");
98 |
99 | b.Property("WalletId")
100 | .HasColumnType("uuid");
101 |
102 | b.HasKey("Id");
103 |
104 | b.HasIndex("WalletId");
105 |
106 | b.ToTable("Transfers");
107 | });
108 |
109 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Aggregates.Wallet", b =>
110 | {
111 | b.HasOne("NPay.Modules.Wallets.Core.Owners.Aggregates.Owner", null)
112 | .WithMany()
113 | .HasForeignKey("OwnerId")
114 | .OnDelete(DeleteBehavior.Cascade)
115 | .IsRequired();
116 | });
117 |
118 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Entities.Transfer", b =>
119 | {
120 | b.HasOne("NPay.Modules.Wallets.Core.Wallets.Aggregates.Wallet", null)
121 | .WithMany("Transfers")
122 | .HasForeignKey("WalletId")
123 | .OnDelete(DeleteBehavior.Cascade)
124 | .IsRequired();
125 | });
126 |
127 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Aggregates.Wallet", b =>
128 | {
129 | b.Navigation("Transfers");
130 | });
131 | #pragma warning restore 612, 618
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/Migrations/20211020191405_Wallets_Init.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace NPay.Modules.Wallets.Infrastructure.DAL.Migrations
5 | {
6 | public partial class Wallets_Init : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.EnsureSchema(
11 | name: "wallets");
12 |
13 | migrationBuilder.CreateTable(
14 | name: "Owners",
15 | schema: "wallets",
16 | columns: table => new
17 | {
18 | Id = table.Column(type: "uuid", nullable: false),
19 | FullName = table.Column(type: "text", nullable: false),
20 | Nationality = table.Column(type: "text", nullable: false),
21 | CreatedAt = table.Column(type: "timestamp without time zone", nullable: false),
22 | VerifiedAt = table.Column(type: "timestamp without time zone", nullable: true),
23 | Version = table.Column(type: "integer", nullable: false)
24 | },
25 | constraints: table =>
26 | {
27 | table.PrimaryKey("PK_Owners", x => x.Id);
28 | });
29 |
30 | migrationBuilder.CreateTable(
31 | name: "Wallets",
32 | schema: "wallets",
33 | columns: table => new
34 | {
35 | Id = table.Column(type: "uuid", nullable: false),
36 | OwnerId = table.Column(type: "uuid", nullable: false),
37 | Currency = table.Column(type: "text", nullable: false),
38 | CreatedAt = table.Column(type: "timestamp without time zone", nullable: false),
39 | Version = table.Column(type: "integer", nullable: false)
40 | },
41 | constraints: table =>
42 | {
43 | table.PrimaryKey("PK_Wallets", x => x.Id);
44 | table.ForeignKey(
45 | name: "FK_Wallets_Owners_OwnerId",
46 | column: x => x.OwnerId,
47 | principalSchema: "wallets",
48 | principalTable: "Owners",
49 | principalColumn: "Id",
50 | onDelete: ReferentialAction.Cascade);
51 | });
52 |
53 | migrationBuilder.CreateTable(
54 | name: "Transfers",
55 | schema: "wallets",
56 | columns: table => new
57 | {
58 | Id = table.Column(type: "uuid", nullable: false),
59 | ReferenceId = table.Column(type: "uuid", nullable: true),
60 | WalletId = table.Column(type: "uuid", nullable: false),
61 | Currency = table.Column(type: "text", nullable: false),
62 | Amount = table.Column(type: "numeric", nullable: false),
63 | Direction = table.Column(type: "integer", nullable: false),
64 | CreatedAt = table.Column(type: "timestamp without time zone", nullable: false)
65 | },
66 | constraints: table =>
67 | {
68 | table.PrimaryKey("PK_Transfers", x => x.Id);
69 | table.ForeignKey(
70 | name: "FK_Transfers_Wallets_WalletId",
71 | column: x => x.WalletId,
72 | principalSchema: "wallets",
73 | principalTable: "Wallets",
74 | principalColumn: "Id",
75 | onDelete: ReferentialAction.Cascade);
76 | });
77 |
78 | migrationBuilder.CreateIndex(
79 | name: "IX_Transfers_WalletId",
80 | schema: "wallets",
81 | table: "Transfers",
82 | column: "WalletId");
83 |
84 | migrationBuilder.CreateIndex(
85 | name: "IX_Wallets_OwnerId",
86 | schema: "wallets",
87 | table: "Wallets",
88 | column: "OwnerId");
89 | }
90 |
91 | protected override void Down(MigrationBuilder migrationBuilder)
92 | {
93 | migrationBuilder.DropTable(
94 | name: "Transfers",
95 | schema: "wallets");
96 |
97 | migrationBuilder.DropTable(
98 | name: "Wallets",
99 | schema: "wallets");
100 |
101 | migrationBuilder.DropTable(
102 | name: "Owners",
103 | schema: "wallets");
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/Migrations/WalletsDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
6 | using NPay.Modules.Wallets.Infrastructure.DAL;
7 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
8 |
9 | namespace NPay.Modules.Wallets.Infrastructure.DAL.Migrations
10 | {
11 | [DbContext(typeof(WalletsDbContext))]
12 | partial class WalletsDbContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasDefaultSchema("wallets")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 63)
20 | .HasAnnotation("ProductVersion", "5.0.11")
21 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
22 |
23 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Owners.Aggregates.Owner", b =>
24 | {
25 | b.Property("Id")
26 | .HasColumnType("uuid");
27 |
28 | b.Property("CreatedAt")
29 | .HasColumnType("timestamp without time zone");
30 |
31 | b.Property("FullName")
32 | .IsRequired()
33 | .HasColumnType("text");
34 |
35 | b.Property("Nationality")
36 | .IsRequired()
37 | .HasColumnType("text");
38 |
39 | b.Property("VerifiedAt")
40 | .HasColumnType("timestamp without time zone");
41 |
42 | b.Property("Version")
43 | .HasColumnType("integer");
44 |
45 | b.HasKey("Id");
46 |
47 | b.ToTable("Owners");
48 | });
49 |
50 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Aggregates.Wallet", b =>
51 | {
52 | b.Property("Id")
53 | .HasColumnType("uuid");
54 |
55 | b.Property("CreatedAt")
56 | .HasColumnType("timestamp without time zone");
57 |
58 | b.Property("Currency")
59 | .IsRequired()
60 | .HasColumnType("text");
61 |
62 | b.Property("OwnerId")
63 | .HasColumnType("uuid");
64 |
65 | b.Property("Version")
66 | .IsConcurrencyToken()
67 | .HasColumnType("integer");
68 |
69 | b.HasKey("Id");
70 |
71 | b.HasIndex("OwnerId");
72 |
73 | b.ToTable("Wallets");
74 | });
75 |
76 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Entities.Transfer", b =>
77 | {
78 | b.Property("Id")
79 | .HasColumnType("uuid");
80 |
81 | b.Property("Amount")
82 | .HasColumnType("numeric");
83 |
84 | b.Property("CreatedAt")
85 | .HasColumnType("timestamp without time zone");
86 |
87 | b.Property("Currency")
88 | .IsRequired()
89 | .HasColumnType("text");
90 |
91 | b.Property("Direction")
92 | .HasColumnType("integer");
93 |
94 | b.Property("ReferenceId")
95 | .HasColumnType("uuid");
96 |
97 | b.Property("WalletId")
98 | .HasColumnType("uuid");
99 |
100 | b.HasKey("Id");
101 |
102 | b.HasIndex("WalletId");
103 |
104 | b.ToTable("Transfers");
105 | });
106 |
107 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Aggregates.Wallet", b =>
108 | {
109 | b.HasOne("NPay.Modules.Wallets.Core.Owners.Aggregates.Owner", null)
110 | .WithMany()
111 | .HasForeignKey("OwnerId")
112 | .OnDelete(DeleteBehavior.Cascade)
113 | .IsRequired();
114 | });
115 |
116 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Entities.Transfer", b =>
117 | {
118 | b.HasOne("NPay.Modules.Wallets.Core.Wallets.Aggregates.Wallet", null)
119 | .WithMany("Transfers")
120 | .HasForeignKey("WalletId")
121 | .OnDelete(DeleteBehavior.Cascade)
122 | .IsRequired();
123 | });
124 |
125 | modelBuilder.Entity("NPay.Modules.Wallets.Core.Wallets.Aggregates.Wallet", b =>
126 | {
127 | b.Navigation("Transfers");
128 | });
129 | #pragma warning restore 612, 618
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/Repositories/OwnerRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.EntityFrameworkCore;
3 | using NPay.Modules.Wallets.Core.Owners.Aggregates;
4 | using NPay.Modules.Wallets.Core.Owners.Repositories;
5 | using NPay.Modules.Wallets.Core.SharedKernel;
6 |
7 | namespace NPay.Modules.Wallets.Infrastructure.DAL.Repositories;
8 |
9 | internal class OwnerRepository : IOwnerRepository
10 | {
11 | private readonly WalletsDbContext _context;
12 | private readonly DbSet _owners;
13 |
14 | public OwnerRepository(WalletsDbContext context)
15 | {
16 | _context = context;
17 | _owners = _context.Owners;
18 | }
19 |
20 | public Task GetAsync(OwnerId id)
21 | => _owners.SingleOrDefaultAsync(x => x.Id.Equals(id));
22 |
23 | public async Task AddAsync(Owner owner)
24 | {
25 | await _owners.AddAsync(owner);
26 | await _context.SaveChangesAsync();
27 | }
28 |
29 | public async Task UpdateAsync(Owner owner)
30 | {
31 | _owners.Update(owner);
32 | await _context.SaveChangesAsync();
33 | }
34 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/Repositories/WalletRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.EntityFrameworkCore;
3 | using NPay.Modules.Wallets.Core.SharedKernel;
4 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
5 | using NPay.Modules.Wallets.Core.Wallets.Repositories;
6 | using NPay.Modules.Wallets.Core.Wallets.ValueObjects;
7 |
8 | namespace NPay.Modules.Wallets.Infrastructure.DAL.Repositories;
9 |
10 | internal class WalletRepository : IWalletRepository
11 | {
12 | private readonly WalletsDbContext _context;
13 | private readonly DbSet _wallets;
14 |
15 | public WalletRepository(WalletsDbContext context)
16 | {
17 | _context = context;
18 | _wallets = _context.Wallets;
19 | }
20 |
21 | public Task GetAsync(WalletId id)
22 | => _wallets
23 | .Include(x => x.Transfers)
24 | .SingleOrDefaultAsync(x => x.Id.Equals(id));
25 |
26 | public Task GetAsync(OwnerId ownerId, Currency currency)
27 | => _wallets
28 | .Include(x => x.Transfers)
29 | .SingleOrDefaultAsync(x => x.OwnerId.Equals(ownerId) && x.Currency.Value.Equals(currency.Value));
30 |
31 | public async Task AddAsync(Wallet wallet)
32 | {
33 | await _wallets.AddAsync(wallet);
34 | await _context.SaveChangesAsync();
35 | }
36 |
37 | public async Task UpdateAsync(Wallet wallet)
38 | {
39 | _wallets.Update(wallet);
40 | await _context.SaveChangesAsync();
41 | }
42 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/DAL/WalletsDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using NPay.Modules.Wallets.Core.Owners.Aggregates;
3 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
4 | using NPay.Modules.Wallets.Core.Wallets.Entities;
5 |
6 | namespace NPay.Modules.Wallets.Infrastructure.DAL;
7 |
8 | internal class WalletsDbContext : DbContext
9 | {
10 | public DbSet Owners { get; set; }
11 | public DbSet Transfers { get; set; }
12 | public DbSet Wallets { get; set; }
13 |
14 | public WalletsDbContext(DbContextOptions options) : base(options)
15 | {
16 | }
17 |
18 | protected override void OnModelCreating(ModelBuilder modelBuilder)
19 | {
20 | modelBuilder.HasDefaultSchema("wallets");
21 | modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using NPay.Modules.Wallets.Application.Wallets.Storage;
3 | using NPay.Modules.Wallets.Core.Owners.Repositories;
4 | using NPay.Modules.Wallets.Core.Wallets.Repositories;
5 | using NPay.Modules.Wallets.Infrastructure.DAL;
6 | using NPay.Modules.Wallets.Infrastructure.DAL.Repositories;
7 | using NPay.Modules.Wallets.Infrastructure.Storage;
8 | using NPay.Shared.Database;
9 |
10 | namespace NPay.Modules.Wallets.Infrastructure;
11 |
12 | public static class Extensions
13 | {
14 | public static IServiceCollection AddInfrastructureLayer(this IServiceCollection services)
15 | {
16 | services.AddPostgres();
17 | services.AddScoped();
18 | services.AddScoped();
19 | services.AddScoped();
20 |
21 | return services;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/NPay.Modules.Wallets.Infrastructure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Infrastructure/Storage/WalletStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Linq.Expressions;
4 | using System.Threading.Tasks;
5 | using Microsoft.EntityFrameworkCore;
6 | using NPay.Modules.Wallets.Application.Wallets.Storage;
7 | using NPay.Modules.Wallets.Core.Wallets.Aggregates;
8 | using NPay.Modules.Wallets.Infrastructure.DAL;
9 |
10 | namespace NPay.Modules.Wallets.Infrastructure.Storage;
11 |
12 | internal sealed class WalletStorage : IWalletStorage
13 | {
14 | private readonly DbSet _wallets;
15 |
16 | public WalletStorage(WalletsDbContext dbContext)
17 | {
18 | _wallets = dbContext.Wallets;
19 | }
20 |
21 | public Task FindAsync(Expression> expression)
22 | => _wallets
23 | .AsNoTracking()
24 | .AsQueryable()
25 | .Where(expression)
26 | .Include(x => x.Transfers)
27 | .SingleOrDefaultAsync();
28 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Shared/DTO/TransferDto.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NPay.Modules.Wallets.Shared.DTO;
4 |
5 | public class TransferDto
6 | {
7 | public string Direction { get; set; }
8 | public Guid TransferId { get; set; }
9 | public Guid? ReferenceId { get; set; }
10 | public Guid WalletId { get; set; }
11 | public decimal Amount { get; set; }
12 | public string Currency { get; set; }
13 | public DateTime CreatedAt { get; set; }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Shared/DTO/WalletDto.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace NPay.Modules.Wallets.Shared.DTO;
5 |
6 | public class WalletDto
7 | {
8 | public Guid WalletId { get; set; }
9 | public Guid OwnerId { get; set; }
10 | public string Currency { get; set; }
11 | public DateTime CreatedAt { get; set; }
12 | public decimal Amount { get; set; }
13 | public List Transfers { get; set; }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Shared/Events/FundsAdded.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Events;
3 |
4 | namespace NPay.Modules.Wallets.Shared.Events;
5 |
6 | public record FundsAdded(Guid WalletId, Guid OwnerId, string Currency, decimal Amount) : IEvent;
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Shared/Events/FundsTransfered.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Events;
3 |
4 | namespace NPay.Modules.Wallets.Shared.Events;
5 |
6 | public record FundsTransferred(Guid FromWalletId, Guid ToWalletId, decimal Amount, string Currency) : IEvent;
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Shared/Events/OwnerVerified.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Events;
3 |
4 | namespace NPay.Modules.Wallets.Shared.Events;
5 |
6 | public record OwnerVerified(Guid OwnerId) : IEvent;
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Shared/Events/WalletAdded.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NPay.Shared.Events;
3 |
4 | namespace NPay.Modules.Wallets.Shared.Events;
5 |
6 | public record WalletAdded(Guid WalletId, Guid OwnerId, string Currency) : IEvent;
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Shared/IWalletsModuleApi.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using NPay.Modules.Wallets.Shared.DTO;
4 |
5 | namespace NPay.Modules.Wallets.Shared;
6 |
7 | public interface IWalletsModuleApi
8 | {
9 | Task GetWalletAsync(Guid walletId);
10 | }
--------------------------------------------------------------------------------
/src/Modules/Wallets/NPay.Modules.Wallets.Shared/NPay.Modules.Wallets.Shared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Modules/Wallets/Wallets.rest:
--------------------------------------------------------------------------------
1 | @url = http://localhost:5000
2 | @email = user1@npay.io
3 | @ownerId = 00000000-0000-0000-0000-000000000000
4 | @fromWalletId = 00000000-0000-0000-0000-000000000000
5 | @toWalletId = 00000000-0000-0000-0000-000000000000
6 |
7 | ###
8 | POST {{url}}/owners
9 | Content-Type: application/json
10 |
11 | {
12 | "email": "{{email}}"
13 | }
14 |
15 | ###
16 | GET {{url}}/wallets/{{fromWalletId}}
17 |
18 | ###
19 | POST {{url}}/wallets
20 | Content-Type: application/json
21 |
22 | {
23 | "ownerId": "{{ownerId}}",
24 | "currency": "EUR"
25 | }
26 |
27 | ###
28 | POST {{url}}/funds
29 | Content-Type: application/json
30 |
31 | {
32 | "walletId": "{{fromWalletId}}",
33 | "amount": 1000
34 | }
35 |
36 | ###
37 | POST {{url}}/funds/transfer
38 | Content-Type: application/json
39 |
40 | {
41 | "fromWalletId": "{{fromWalletId}}",
42 | "toWalletId": "{{toWalletId}}",
43 | "amount": 100
44 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Commands/CommandDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace NPay.Shared.Commands;
7 |
8 | internal sealed class CommandDispatcher : ICommandDispatcher
9 | {
10 | private readonly IServiceProvider _serviceProvider;
11 |
12 | public CommandDispatcher(IServiceProvider serviceProvider)
13 | => _serviceProvider = serviceProvider;
14 |
15 | public async Task SendAsync(TCommand command, CancellationToken cancellationToken = default) where TCommand : class, ICommand
16 | {
17 | if (command is null)
18 | {
19 | return;
20 | }
21 |
22 | using var scope = _serviceProvider.CreateScope();
23 | var handler = scope.ServiceProvider.GetRequiredService>();
24 | await handler.HandleAsync(command, cancellationToken);
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Commands/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace NPay.Shared.Commands;
5 |
6 | internal static class Extensions
7 | {
8 | public static IServiceCollection AddCommands(this IServiceCollection services)
9 | {
10 | services.AddSingleton();
11 | services.Scan(s => s.FromAssemblies(AppDomain.CurrentDomain.GetAssemblies())
12 | .AddClasses(c => c.AssignableTo(typeof(ICommandHandler<>)))
13 | .AsImplementedInterfaces()
14 | .WithScopedLifetime());
15 |
16 | return services;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Commands/ICommand.cs:
--------------------------------------------------------------------------------
1 | namespace NPay.Shared.Commands;
2 |
3 | //Marker
4 | public interface ICommand
5 | {
6 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Commands/ICommandDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace NPay.Shared.Commands;
5 |
6 | public interface ICommandDispatcher
7 | {
8 | Task SendAsync(TCommand command, CancellationToken cancellationToken = default) where TCommand : class, ICommand;
9 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Commands/ICommandHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace NPay.Shared.Commands;
5 |
6 | public interface ICommandHandler where TCommand : class, ICommand
7 | {
8 | Task HandleAsync(TCommand command, CancellationToken cancellationToken = default);
9 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Database/DbContextAppInitializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace NPay.Shared.Database;
11 |
12 | internal sealed class DbContextAppInitializer : IHostedService
13 | {
14 | private readonly IServiceProvider _serviceProvider;
15 | private readonly ILogger _logger;
16 |
17 | public DbContextAppInitializer(IServiceProvider serviceProvider, ILogger logger)
18 | {
19 | _serviceProvider = serviceProvider;
20 | _logger = logger;
21 | }
22 |
23 | public async Task StartAsync(CancellationToken cancellationToken)
24 | {
25 | var dbContextTypes = AppDomain.CurrentDomain.GetAssemblies()
26 | .SelectMany(x => x.GetTypes())
27 | .Where(x => typeof(DbContext).IsAssignableFrom(x) && !x.IsInterface && x != typeof(DbContext));
28 |
29 | using var scope = _serviceProvider.CreateScope();
30 | foreach (var dbContextType in dbContextTypes)
31 | {
32 | var dbContext = scope.ServiceProvider.GetService(dbContextType) as DbContext;
33 | if (dbContext is null)
34 | {
35 | continue;
36 | }
37 |
38 | _logger.LogInformation($"Running DB context: {dbContext.GetType().Name}...");
39 | await dbContext.Database.MigrateAsync(cancellationToken);
40 | }
41 | }
42 |
43 | public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
44 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Database/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace NPay.Shared.Database;
7 |
8 | public static class Extensions
9 | {
10 | private const string SectionName = "postgres";
11 |
12 | internal static IServiceCollection AddPostgres(this IServiceCollection services, IConfiguration configuration)
13 | {
14 | services.Configure(configuration.GetSection(SectionName));
15 | services.AddHostedService();
16 |
17 | // Temporary fix for EF Core issue related to https://github.com/npgsql/efcore.pg/issues/2000
18 | AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
19 |
20 | return services;
21 | }
22 |
23 | public static IServiceCollection AddPostgres(this IServiceCollection services) where T : DbContext
24 | {
25 | var configuration = services.BuildServiceProvider().GetRequiredService();
26 | var connectionString = configuration[$"{SectionName}:{nameof(PostgresOptions.ConnectionString)}"];
27 | services.AddDbContext(x => x.UseNpgsql(connectionString));
28 |
29 | return services;
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Database/PostgresOptions.cs:
--------------------------------------------------------------------------------
1 | namespace NPay.Shared.Database;
2 |
3 | internal sealed class PostgresOptions
4 | {
5 | public string ConnectionString { get; set; }
6 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Dispatchers/IDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Shared.Commands;
4 | using NPay.Shared.Events;
5 | using NPay.Shared.Queries;
6 |
7 | namespace NPay.Shared.Dispatchers;
8 |
9 | public interface IDispatcher
10 | {
11 | Task SendAsync(T command, CancellationToken cancellationToken = default) where T : class, ICommand;
12 | Task PublishAsync(T @event, CancellationToken cancellationToken = default) where T : class, IEvent;
13 | Task QueryAsync(IQuery query, CancellationToken cancellationToken = default);
14 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Dispatchers/InMemoryDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Shared.Commands;
4 | using NPay.Shared.Events;
5 | using NPay.Shared.Queries;
6 |
7 | namespace NPay.Shared.Dispatchers;
8 |
9 | internal sealed class InMemoryDispatcher : IDispatcher
10 | {
11 | private readonly ICommandDispatcher _commandDispatcher;
12 | private readonly IEventDispatcher _eventDispatcher;
13 | private readonly IQueryDispatcher _queryDispatcher;
14 |
15 | public InMemoryDispatcher(ICommandDispatcher commandDispatcher, IEventDispatcher eventDispatcher,
16 | IQueryDispatcher queryDispatcher)
17 | {
18 | _commandDispatcher = commandDispatcher;
19 | _eventDispatcher = eventDispatcher;
20 | _queryDispatcher = queryDispatcher;
21 | }
22 |
23 | public Task SendAsync(T command, CancellationToken cancellationToken = default) where T : class, ICommand
24 | => _commandDispatcher.SendAsync(command, cancellationToken);
25 |
26 | public Task PublishAsync(T @event, CancellationToken cancellationToken = default) where T : class, IEvent
27 | => _eventDispatcher.PublishAsync(@event, cancellationToken);
28 |
29 | public Task QueryAsync(IQuery query, CancellationToken cancellationToken = default)
30 | => _queryDispatcher.QueryAsync(query, cancellationToken);
31 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Events/EventDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.Extensions.DependencyInjection;
6 |
7 | namespace NPay.Shared.Events;
8 |
9 | internal sealed class EventDispatcher : IEventDispatcher
10 | {
11 | private readonly IServiceProvider _serviceProvider;
12 |
13 | public EventDispatcher(IServiceProvider serviceProvider)
14 | => _serviceProvider = serviceProvider;
15 |
16 | public async Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default)
17 | where TEvent : class, IEvent
18 | {
19 | if (typeof(TEvent) == typeof(IEvent))
20 | {
21 | await DispatchDynamicallyAsync(@event, cancellationToken);
22 | return;
23 | }
24 |
25 | using var scope = _serviceProvider.CreateScope();
26 | var handlers = scope.ServiceProvider.GetServices>();
27 | var tasks = handlers.Select(x => x.HandleAsync(@event, cancellationToken));
28 | await Task.WhenAll(tasks);
29 | }
30 |
31 | private async Task DispatchDynamicallyAsync(IEvent @event, CancellationToken cancellationToken = default)
32 | {
33 | using var scope = _serviceProvider.CreateScope();
34 | var handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType());
35 | var handlers = scope.ServiceProvider.GetServices(handlerType);
36 | var method = handlerType.GetMethod(nameof(IEventHandler.HandleAsync));
37 | if (method is null)
38 | {
39 | throw new InvalidOperationException($"Event handler for '{@event.GetType().Name}' is invalid.");
40 | }
41 |
42 | var tasks = handlers.Select(x => (Task)method.Invoke(x, new object[] { @event, cancellationToken }));
43 | await Task.WhenAll(tasks);
44 | }
45 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Events/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace NPay.Shared.Events;
5 |
6 | internal static class Extensions
7 | {
8 | public static IServiceCollection AddEvents(this IServiceCollection services)
9 | {
10 | services.AddSingleton();
11 | services.Scan(s => s.FromAssemblies(AppDomain.CurrentDomain.GetAssemblies())
12 | .AddClasses(c => c.AssignableTo(typeof(IEventHandler<>)))
13 | .AsImplementedInterfaces()
14 | .WithScopedLifetime());
15 |
16 | return services;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Events/IEvent.cs:
--------------------------------------------------------------------------------
1 | namespace NPay.Shared.Events;
2 |
3 | // Marker
4 | public interface IEvent
5 | {
6 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Events/IEventDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace NPay.Shared.Events;
5 |
6 | public interface IEventDispatcher
7 | {
8 | // Task PublishAsync(IEvent @event, CancellationToken cancellationToken = default);
9 |
10 | Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default)
11 | where TEvent : class, IEvent;
12 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Events/IEventHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace NPay.Shared.Events;
5 |
6 | public interface IEventHandler where TEvent : class, IEvent
7 | {
8 | Task HandleAsync(TEvent @event, CancellationToken cancellationToken = default);
9 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Exceptions/ErrorHandlerMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace NPay.Shared.Exceptions;
8 |
9 | internal sealed class ErrorHandlerMiddleware : IMiddleware
10 | {
11 | private readonly IExceptionToResponseMapper _exceptionToResponseMapper;
12 | private readonly ILogger _logger;
13 |
14 | public ErrorHandlerMiddleware(IExceptionToResponseMapper exceptionToResponseMapper,
15 | ILogger logger)
16 | {
17 | _exceptionToResponseMapper = exceptionToResponseMapper;
18 | _logger = logger;
19 | }
20 |
21 | public async Task InvokeAsync(HttpContext context, RequestDelegate next)
22 | {
23 | try
24 | {
25 | await next(context);
26 | }
27 | catch (Exception exception)
28 | {
29 | _logger.LogError(exception, exception.Message);
30 | await HandleErrorAsync(context, exception);
31 | }
32 | }
33 |
34 | private async Task HandleErrorAsync(HttpContext context, Exception exception)
35 | {
36 | var errorResponse = _exceptionToResponseMapper.Map(exception);
37 | context.Response.StatusCode = (int) (errorResponse?.StatusCode ?? HttpStatusCode.InternalServerError);
38 | var response = errorResponse?.Response;
39 | if (response is null)
40 | {
41 | return;
42 | }
43 |
44 | await context.Response.WriteAsJsonAsync(response);
45 | }
46 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Exceptions/ExceptionResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace NPay.Shared.Exceptions;
4 |
5 | public sealed record ExceptionResponse(object Response, HttpStatusCode StatusCode);
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Exceptions/ExceptionToResponseMapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Net;
4 | using Humanizer;
5 |
6 | namespace NPay.Shared.Exceptions;
7 |
8 | internal sealed class ExceptionToResponseMapper : IExceptionToResponseMapper
9 | {
10 | private static readonly ConcurrentDictionary Codes = new();
11 |
12 | public ExceptionResponse Map(Exception exception)
13 | => exception switch
14 | {
15 | NPayException ex => new ExceptionResponse(new ErrorsResponse(new Error(GetErrorCode(ex), ex.Message))
16 | , HttpStatusCode.BadRequest),
17 | _ => new ExceptionResponse(new ErrorsResponse(new Error("error", "There was an error.")),
18 | HttpStatusCode.InternalServerError)
19 | };
20 |
21 | private record Error(string Code, string Message);
22 |
23 | private record ErrorsResponse(params Error[] Errors);
24 |
25 | private static string GetErrorCode(object exception)
26 | {
27 | var type = exception.GetType();
28 | return Codes.GetOrAdd(type, type.Name.Underscore().Replace("_exception", string.Empty));
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Exceptions/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace NPay.Shared.Exceptions;
5 |
6 | internal static class Extensions
7 | {
8 | public static IServiceCollection AddErrorHandling(this IServiceCollection services)
9 | => services
10 | .AddScoped()
11 | .AddSingleton();
12 |
13 | public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder app)
14 | => app.UseMiddleware();
15 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Exceptions/IExceptionToResponseMapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NPay.Shared.Exceptions;
4 |
5 | internal interface IExceptionToResponseMapper
6 | {
7 | ExceptionResponse Map(Exception exception);
8 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Exceptions/NPayException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NPay.Shared.Exceptions;
4 |
5 | public abstract class NPayException : Exception
6 | {
7 | protected NPayException(string message) : base(message)
8 | {
9 | }
10 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.OpenApi.Models;
6 | using NPay.Shared.Commands;
7 | using NPay.Shared.Database;
8 | using NPay.Shared.Dispatchers;
9 | using NPay.Shared.Events;
10 | using NPay.Shared.Exceptions;
11 | using NPay.Shared.Messaging;
12 | using NPay.Shared.Queries;
13 | using NPay.Shared.Time;
14 |
15 | namespace NPay.Shared;
16 |
17 | public static class Extensions
18 | {
19 | private const string ApiTitle = "NPay API";
20 | private const string ApiVersion = "v1";
21 |
22 | public static IServiceCollection AddSharedFramework(this IServiceCollection services, IConfiguration configuration)
23 | {
24 | services.AddErrorHandling();
25 | services.AddCommands();
26 | services.AddEvents();
27 | services.AddQueries();
28 | services.AddMessaging();
29 | services.AddPostgres(configuration);
30 | services.AddControllers();
31 | services.AddSingleton();
32 | services.AddSingleton();
33 | services.AddSingleton();
34 | services.AddSwaggerGen(swagger =>
35 | {
36 | swagger.EnableAnnotations();
37 | swagger.CustomSchemaIds(x => x.FullName);
38 | swagger.SwaggerDoc(ApiVersion, new OpenApiInfo
39 | {
40 | Title = ApiTitle,
41 | Version = ApiVersion
42 | });
43 | });
44 |
45 | return services;
46 | }
47 |
48 | public static IApplicationBuilder UseSharedFramework(this IApplicationBuilder app)
49 | {
50 | app.UseErrorHandling();
51 | app.UseSwagger();
52 | app.UseReDoc(reDoc =>
53 | {
54 | reDoc.RoutePrefix = "docs";
55 | reDoc.SpecUrl($"/swagger/{ApiVersion}/swagger.json");
56 | reDoc.DocumentTitle = ApiTitle;
57 | });
58 | app.UseRouting();
59 |
60 | return app;
61 | }
62 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Messaging/AsyncEventDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Shared.Events;
4 |
5 | namespace NPay.Shared.Messaging;
6 |
7 | internal sealed class AsyncEventDispatcher : IAsyncEventDispatcher
8 | {
9 | private readonly IEventChannel _channel;
10 |
11 | public AsyncEventDispatcher(IEventChannel channel)
12 | {
13 | _channel = channel;
14 | }
15 |
16 | public async Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default)
17 | where TEvent : class, IEvent
18 | {
19 | await _channel.Writer.WriteAsync(@event, cancellationToken);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Messaging/EventChannel.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Channels;
2 | using NPay.Shared.Events;
3 |
4 | namespace NPay.Shared.Messaging;
5 |
6 | internal sealed class EventChannel : IEventChannel
7 | {
8 | private readonly Channel _messages = Channel.CreateUnbounded();
9 |
10 | public ChannelReader Reader => _messages.Reader;
11 | public ChannelWriter Writer => _messages.Writer;
12 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Messaging/EventDispatcherJob.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Microsoft.Extensions.Hosting;
5 | using Microsoft.Extensions.Logging;
6 | using NPay.Shared.Events;
7 |
8 | namespace NPay.Shared.Messaging;
9 |
10 | internal sealed class EventDispatcherJob : BackgroundService
11 | {
12 | private readonly IEventChannel _eventChannel;
13 | private readonly IEventDispatcher _eventDispatcher;
14 | private readonly ILogger _logger;
15 |
16 | public EventDispatcherJob(IEventChannel eventChannel, IEventDispatcher eventDispatcher,
17 | ILogger logger)
18 | {
19 | _eventChannel = eventChannel;
20 | _eventDispatcher = eventDispatcher;
21 | _logger = logger;
22 | }
23 |
24 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
25 | {
26 | await foreach (var @event in _eventChannel.Reader.ReadAllAsync(stoppingToken))
27 | {
28 | try
29 | {
30 | await _eventDispatcher.PublishAsync(@event, stoppingToken);
31 | }
32 | catch (Exception exception)
33 | {
34 | _logger.LogError(exception, exception.Message);
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Messaging/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 |
3 | namespace NPay.Shared.Messaging;
4 |
5 | internal static class Extensions
6 | {
7 | private const string SectionName = "messaging";
8 |
9 | public static IServiceCollection AddMessaging(this IServiceCollection services)
10 | {
11 | services.AddTransient();
12 | services.AddTransient();
13 | services.AddSingleton();
14 | services.AddHostedService();
15 |
16 | return services;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Messaging/IAsyncEventDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using NPay.Shared.Events;
4 |
5 | namespace NPay.Shared.Messaging;
6 |
7 | internal interface IAsyncEventDispatcher
8 | {
9 | Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default)
10 | where TEvent : class, IEvent;
11 | }
--------------------------------------------------------------------------------
/src/Shared/NPay.Shared/Messaging/IEventChannel.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Channels;
2 | using NPay.Shared.Events;
3 |
4 | namespace NPay.Shared.Messaging;
5 |
6 | internal interface IEventChannel
7 | {
8 | ChannelReader