diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ffcf08e..643903c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -24,7 +24,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/delete-bin-obj-folders.js b/delete-bin-obj-folders.js new file mode 100644 index 0000000..040ffec --- /dev/null +++ b/delete-bin-obj-folders.js @@ -0,0 +1,37 @@ +const { resolve } = require("path"); +const { readdir, rmdir } = require("fs").promises; + +async function* removeFolder(dir) { + await rmdir(dir, { recursive: true }); + yield dir; +} + +async function* getBinObj(dir) { + const dirents = await readdir(dir, { withFileTypes: true }); + + for (const dirent of dirents) { + if (!dirent.isDirectory()) continue; + const name = dirent.name; + + if (name === "node_modules") continue; + + const res = resolve(dir, dirent.name); + + if (name === "bin" || name === "obj") { + yield* removeFolder(res); + continue; + } + + yield* getBinObj(res); + } +} + +(async () => { + console.log("\x1b[36m%s\x1b[0m", "Deleting all BIN and OBJ folders..."); + + for await (const dir of getBinObj(".")) { + console.log("\x1b[33m%s\x1b[0m", `Removed: ${dir}`); + } + + console.log("\x1b[36m%s\x1b[0m", "All BIN and OBJ folders are deleted."); +})(); diff --git a/etc/azure/deploy-to-k8s.yml b/etc/azure/deploy-to-k8s.yml index de891da..92373ce 100644 --- a/etc/azure/deploy-to-k8s.yml +++ b/etc/azure/deploy-to-k8s.yml @@ -7,6 +7,14 @@ pool: vmImage: 'ubuntu-latest' steps: +- task: UseDotNet@2 + displayName: 'Use .NET sdk' + inputs: + packageType: sdk + version: 6.0.x + installationPath: $(Agent.ToolsDirectory)/dotnet + includePreviewVersions: true + - task: PowerShell@2 displayName: Build docker images inputs: @@ -53,4 +61,4 @@ steps: chartName: 'etc/k8s/helm-chart/eventhub' releaseName: 'eh-az' valueFile: 'etc/k8s/helm-chart/eventhub/values.azure.yaml' - arguments: '--namespace eventhub --create-namespace --set global.eventHubImageVersion=$(version)' \ No newline at end of file + arguments: '--timeout 20m --namespace eventhub --create-namespace --set global.eventHubImageVersion=$(version)' \ No newline at end of file diff --git a/etc/k8s/README.md b/etc/k8s/README.md index 7810dae..15d3797 100644 --- a/etc/k8s/README.md +++ b/etc/k8s/README.md @@ -2,10 +2,11 @@ * Docker Desktop with Kubernetes enabled * Install [NGINX ingress](https://kubernetes.github.io/ingress-nginx/deploy/) for k8s +* Install [Helm](https://helm.sh/docs/intro/install/) for running helm charts ### How to run? -* Add entries to the hosts file (in Windows: `C:\Windows\System32\drivers\etc\hosts`): +* Add entries to the hosts file (in Windows: `C:\Windows\System32\drivers\etc\hosts`, in MacOs: `/etc/hosts`): ```` 127.0.0.1 eh-st-account @@ -16,6 +17,8 @@ ```` * Run `build-images.ps1` in the `scripts` directory. -* Run `helm install eh-st eventhub` in the `helm-chart` directory. +* Run `minikube-load-images.ps1` in the `scripts` directory(only for `minikube`). +* Run `deploy-staging.ps1` in the `helm-chart` directory. It is deployed with the `eventhub` namespace. +* *You may wait ~30 seconds on first run for preparing the database*. * Browse https://eh-st-www and https://eh-st-admin * Username: `admin`, password: `1q2w3E*`. diff --git a/etc/k8s/helm-chart/eventhub/charts/postgresql/templates/postgresql.yaml b/etc/k8s/helm-chart/eventhub/charts/postgresql/templates/postgresql.yaml index 3d37d3d..1dfc47a 100644 --- a/etc/k8s/helm-chart/eventhub/charts/postgresql/templates/postgresql.yaml +++ b/etc/k8s/helm-chart/eventhub/charts/postgresql/templates/postgresql.yaml @@ -14,7 +14,7 @@ spec: app: {{ .Release.Name }}-{{ .Chart.Name }} spec: containers: - - image: postgres + - image: postgres:13-bullseye name: {{ .Release.Name }}-{{ .Chart.Name }} {{- if eq .Release.Name "eh-az" }} volumeMounts: diff --git a/global.json b/global.json index f49d7df..5eb4fe8 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "5.0.201", + "version": "6.0.100-rc.1.21458.32", "rollForward": "latestFeature" } } diff --git a/modules/payment/.gitattributes b/modules/payment/.gitattributes new file mode 100644 index 0000000..c941e52 --- /dev/null +++ b/modules/payment/.gitattributes @@ -0,0 +1 @@ +**/wwwroot/libs/** linguist-vendored diff --git a/modules/payment/.gitignore b/modules/payment/.gitignore new file mode 100644 index 0000000..7127e17 --- /dev/null +++ b/modules/payment/.gitignore @@ -0,0 +1,259 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# Payment +host/Payment.IdentityServer/Logs/logs.txt +host/Payment.HttpApi.Host/Logs/logs.txt +host/Payment.Web.Host/Logs/logs.txt +host/Payment.Web.Unified/Logs/logs.txt +host/Payment.Blazor.Server.Host/Logs/logs.txt \ No newline at end of file diff --git a/modules/payment/.prettierrc b/modules/payment/.prettierrc new file mode 100644 index 0000000..56af76b --- /dev/null +++ b/modules/payment/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "useTabs": false, + "tabWidth": 4 +} diff --git a/modules/payment/NuGet.Config b/modules/payment/NuGet.Config new file mode 100644 index 0000000..be8a1ec --- /dev/null +++ b/modules/payment/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/modules/payment/Payment.sln b/modules/payment/Payment.sln new file mode 100644 index 0000000..d9c494e --- /dev/null +++ b/modules/payment/Payment.sln @@ -0,0 +1,176 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31808.319 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.Domain.Shared", "src\Payment.Domain.Shared\Payment.Domain.Shared.csproj", "{D64C1577-4929-4B60-939E-96DE1534891A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.Domain", "src\Payment.Domain\Payment.Domain.csproj", "{F2840BC7-0188-4606-9126-DADD0F5ABF7A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.Application.Contracts", "src\Payment.Application.Contracts\Payment.Application.Contracts.csproj", "{BD65D04F-08D5-40C1-8C24-77CA0BACB877}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.Application", "src\Payment.Application\Payment.Application.csproj", "{78040F9E-3501-4A40-82DF-00A597710F35}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{649A3FFA-182F-4E56-9717-E6A9A2BEC545}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.EntityFrameworkCore", "src\Payment.EntityFrameworkCore\Payment.EntityFrameworkCore.csproj", "{0CE86223-D31D-4315-A1F5-87BA3EE1B844}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.HttpApi", "src\Payment.HttpApi\Payment.HttpApi.csproj", "{077AA5F8-8B61-420C-A6B5-0150A66FDB34}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.TestBase", "test\Payment.TestBase\Payment.TestBase.csproj", "{C5BB573D-3030-4BCB-88B7-F6A85C32766C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.EntityFrameworkCore.Tests", "test\Payment.EntityFrameworkCore.Tests\Payment.EntityFrameworkCore.Tests.csproj", "{527F645C-C1FC-406E-8479-81386C8ECF13}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.Domain.Tests", "test\Payment.Domain.Tests\Payment.Domain.Tests.csproj", "{E60895E5-79C4-447D-88B7-85CB5BA336A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.Application.Tests", "test\Payment.Application.Tests\Payment.Application.Tests.csproj", "{90CB5DC4-C040-45C7-8900-9688B26405BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.Web", "src\Payment.Web\Payment.Web.csproj", "{3B7B6317-1B85-4164-8E11-75574F80AE17}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{E46CF089-D16A-4761-BE24-1B1B1D49225A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "www", "www", "{4EF8B98A-E7A3-48A6-9C1F-10DE32001213}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{5B39D229-146D-423E-9E50-91471851824E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{EFBC83DB-A546-471E-99E4-4948C3ADCAA1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "www", "www", "{D3FD0217-A8C7-4BAF-BF77-962F1B055515}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{FC718EF0-43EB-4767-9578-95FA0B3727A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.HttpApi.Client", "src\Payment.HttpApi.Client\Payment.HttpApi.Client.csproj", "{3FF65447-69D5-4115-8398-8156FB381215}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.BackgroundServices", "src\Payment.BackgroundServices\Payment.BackgroundServices.csproj", "{98CAA357-46A3-48EC-B5D9-27C706862351}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.Admin.Application.Contracts", "src\Payment.Admin.Application.Contracts\Payment.Admin.Application.Contracts.csproj", "{B4D6C73B-2848-4B0C-BFA5-7EBEA59F6A75}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.Admin.Application", "src\Payment.Admin.Application\Payment.Admin.Application.csproj", "{267E26D0-3401-4945-AD58-30FE482566DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.Admin.HttpApi", "src\Payment.Admin.HttpApi\Payment.Admin.HttpApi.csproj", "{046100DA-71ED-43C5-8C33-D424D678DBEA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.Admin.HttpApi.Client", "src\Payment.Admin.HttpApi.Client\Payment.Admin.HttpApi.Client.csproj", "{A791943D-3F0B-41E3-AE73-15A60180B4A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.Admin.Blazor", "src\Payment.Admin.Blazor\Payment.Admin.Blazor.csproj", "{7C6D6C96-0816-43F3-81C8-44CBC18434DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payment.Admin.Application.Tests", "test\Payment.Admin.Application.Tests\Payment.Admin.Application.Tests.csproj", "{E31DBDFE-1CC7-4E1B-B65C-C3810BF85981}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D64C1577-4929-4B60-939E-96DE1534891A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Release|Any CPU.Build.0 = Release|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Release|Any CPU.Build.0 = Release|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Release|Any CPU.Build.0 = Release|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Release|Any CPU.Build.0 = Release|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Release|Any CPU.Build.0 = Release|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Release|Any CPU.Build.0 = Release|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Release|Any CPU.Build.0 = Release|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Release|Any CPU.Build.0 = Release|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Release|Any CPU.Build.0 = Release|Any CPU + {90CB5DC4-C040-45C7-8900-9688B26405BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90CB5DC4-C040-45C7-8900-9688B26405BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90CB5DC4-C040-45C7-8900-9688B26405BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90CB5DC4-C040-45C7-8900-9688B26405BC}.Release|Any CPU.Build.0 = Release|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Release|Any CPU.Build.0 = Release|Any CPU + {3FF65447-69D5-4115-8398-8156FB381215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FF65447-69D5-4115-8398-8156FB381215}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FF65447-69D5-4115-8398-8156FB381215}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FF65447-69D5-4115-8398-8156FB381215}.Release|Any CPU.Build.0 = Release|Any CPU + {98CAA357-46A3-48EC-B5D9-27C706862351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98CAA357-46A3-48EC-B5D9-27C706862351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98CAA357-46A3-48EC-B5D9-27C706862351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98CAA357-46A3-48EC-B5D9-27C706862351}.Release|Any CPU.Build.0 = Release|Any CPU + {B4D6C73B-2848-4B0C-BFA5-7EBEA59F6A75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4D6C73B-2848-4B0C-BFA5-7EBEA59F6A75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4D6C73B-2848-4B0C-BFA5-7EBEA59F6A75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4D6C73B-2848-4B0C-BFA5-7EBEA59F6A75}.Release|Any CPU.Build.0 = Release|Any CPU + {267E26D0-3401-4945-AD58-30FE482566DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {267E26D0-3401-4945-AD58-30FE482566DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {267E26D0-3401-4945-AD58-30FE482566DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {267E26D0-3401-4945-AD58-30FE482566DD}.Release|Any CPU.Build.0 = Release|Any CPU + {046100DA-71ED-43C5-8C33-D424D678DBEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {046100DA-71ED-43C5-8C33-D424D678DBEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {046100DA-71ED-43C5-8C33-D424D678DBEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {046100DA-71ED-43C5-8C33-D424D678DBEA}.Release|Any CPU.Build.0 = Release|Any CPU + {A791943D-3F0B-41E3-AE73-15A60180B4A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A791943D-3F0B-41E3-AE73-15A60180B4A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A791943D-3F0B-41E3-AE73-15A60180B4A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A791943D-3F0B-41E3-AE73-15A60180B4A5}.Release|Any CPU.Build.0 = Release|Any CPU + {7C6D6C96-0816-43F3-81C8-44CBC18434DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C6D6C96-0816-43F3-81C8-44CBC18434DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C6D6C96-0816-43F3-81C8-44CBC18434DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C6D6C96-0816-43F3-81C8-44CBC18434DE}.Release|Any CPU.Build.0 = Release|Any CPU + {E31DBDFE-1CC7-4E1B-B65C-C3810BF85981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E31DBDFE-1CC7-4E1B-B65C-C3810BF85981}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E31DBDFE-1CC7-4E1B-B65C-C3810BF85981}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E31DBDFE-1CC7-4E1B-B65C-C3810BF85981}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D64C1577-4929-4B60-939E-96DE1534891A} = {E46CF089-D16A-4761-BE24-1B1B1D49225A} + {F2840BC7-0188-4606-9126-DADD0F5ABF7A} = {E46CF089-D16A-4761-BE24-1B1B1D49225A} + {BD65D04F-08D5-40C1-8C24-77CA0BACB877} = {4EF8B98A-E7A3-48A6-9C1F-10DE32001213} + {78040F9E-3501-4A40-82DF-00A597710F35} = {4EF8B98A-E7A3-48A6-9C1F-10DE32001213} + {0CE86223-D31D-4315-A1F5-87BA3EE1B844} = {E46CF089-D16A-4761-BE24-1B1B1D49225A} + {077AA5F8-8B61-420C-A6B5-0150A66FDB34} = {4EF8B98A-E7A3-48A6-9C1F-10DE32001213} + {C5BB573D-3030-4BCB-88B7-F6A85C32766C} = {EFBC83DB-A546-471E-99E4-4948C3ADCAA1} + {527F645C-C1FC-406E-8479-81386C8ECF13} = {EFBC83DB-A546-471E-99E4-4948C3ADCAA1} + {E60895E5-79C4-447D-88B7-85CB5BA336A4} = {EFBC83DB-A546-471E-99E4-4948C3ADCAA1} + {90CB5DC4-C040-45C7-8900-9688B26405BC} = {D3FD0217-A8C7-4BAF-BF77-962F1B055515} + {3B7B6317-1B85-4164-8E11-75574F80AE17} = {4EF8B98A-E7A3-48A6-9C1F-10DE32001213} + {E46CF089-D16A-4761-BE24-1B1B1D49225A} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {4EF8B98A-E7A3-48A6-9C1F-10DE32001213} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {5B39D229-146D-423E-9E50-91471851824E} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {EFBC83DB-A546-471E-99E4-4948C3ADCAA1} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {D3FD0217-A8C7-4BAF-BF77-962F1B055515} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {FC718EF0-43EB-4767-9578-95FA0B3727A5} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {3FF65447-69D5-4115-8398-8156FB381215} = {4EF8B98A-E7A3-48A6-9C1F-10DE32001213} + {98CAA357-46A3-48EC-B5D9-27C706862351} = {E46CF089-D16A-4761-BE24-1B1B1D49225A} + {B4D6C73B-2848-4B0C-BFA5-7EBEA59F6A75} = {5B39D229-146D-423E-9E50-91471851824E} + {267E26D0-3401-4945-AD58-30FE482566DD} = {5B39D229-146D-423E-9E50-91471851824E} + {046100DA-71ED-43C5-8C33-D424D678DBEA} = {5B39D229-146D-423E-9E50-91471851824E} + {A791943D-3F0B-41E3-AE73-15A60180B4A5} = {5B39D229-146D-423E-9E50-91471851824E} + {7C6D6C96-0816-43F3-81C8-44CBC18434DE} = {5B39D229-146D-423E-9E50-91471851824E} + {E31DBDFE-1CC7-4E1B-B65C-C3810BF85981} = {FC718EF0-43EB-4767-9578-95FA0B3727A5} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6AAFA1C6-603E-13FA-45E5-7910AA9F661D} + EndGlobalSection +EndGlobal diff --git a/modules/payment/Payment.sln.DotSettings b/modules/payment/Payment.sln.DotSettings new file mode 100644 index 0000000..cb0b2c9 --- /dev/null +++ b/modules/payment/Payment.sln.DotSettings @@ -0,0 +1,23 @@ + + True + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + Required + Required + Required + Required + False + True + False + False + True + False + False + SQL + \ No newline at end of file diff --git a/modules/payment/README.md b/modules/payment/README.md new file mode 100644 index 0000000..6ea8fd4 --- /dev/null +++ b/modules/payment/README.md @@ -0,0 +1,97 @@ +# Payment Module + +Payment module provides an API to make payments via using PayPal easily. + + + +## Installation + +TODO + + + +## Usage + +1. Create a Payment Request via using `IPaymentRequestAppService` + + + +2. Build an URL to redirect to checkout page via using `IPaymentUrlBuilder` + + ```csharp + public class MyPageModel : AbpPageModel + { + protected IPaymentRequestAppService PaymentRequestAppService { get; } + protected IPaymentUrlBuilder PaymentUrlBuilder { get; } + + public MyPageModel( + IPaymentRequestAppService paymentRequestAppService, + IPaymentUrlBuilder paymentUrlBuilder) + { + PaymentRequestAppService = paymentRequestAppService; + PaymentUrlBuilder = paymentUrlBuilder; + } + + public async Task OnPostAsync() + { + var paymentRequest = await PaymentRequestAppService.CreateAsync(new PaymentRequestCreationDto + { + Amount = 9.90m, + CustomerId = CurrentUser.Id.ToString(), + ProductId = "UniqueProductId", + ProductName = "Awesome Product" + }); + + var checkoutUrl = PaymentUrlBuilder.BuildCheckoutUrl(paymentRequest.Id).AbsoluteUri; + + Response.Redirect(checkoutUrl); + } + } + ``` + + + + +--- + + + +## Distributed Events + +- `Payment.Completed` (**PaymentRequestCompletedEto**): Published when a payment is completed. Completion can be triggered via webhook or callback. Source doesn't affect to the event. Event will be triggered once. + - `PaymentRequestId`: Represents PaymentRequest entity Id. + - `ExtraProperties`: Represents ExtraProperties of PaymentRequest entity. You can use this properties to find your related objects to that payment. +- `Payment.Failed`(**PaymentRequestFailedEto**): Published when a payment is failed. + - `PaymentRequestId`: Represents PaymentRequest entity Id. + - `FailReason`: Reason of failure from payment provider (PayPal) + - `ExtraProperties`: Represents ExtraProperties of PaymentRequest entity. + + + +## Webhook Handling +You have to make some configuration for webhooks on PayPal Dashboard. + +_If you test it locally, you can use [ngrok](https://ngrok.com/) to open your localhost to world_ + +0. Deploy eventhub or open your ip:port to public. Your HttpApi.Host must be accessible from all over the world. + +1. Go to [Application List](https://developer.paypal.com/developer/applications). Find your app and go details. _(You can use my account below)_ + + PayPal Account Credentials + +> `info@enisnecipoglu.com` +> `1q2w3E**` + +> Use following temp number for two factor authentication verification: +> https://receive-smss.com/sms/48722717428/ + +> _(Yes the star (*) is doubled. min password length requirement was 8)_ + + + +2. At the bottom of page you'll see the Webhooks section + + +3. Click to **Add Webhook** button and use following values: + - **Webhook URL**: `https://yourhttpapihosturl.com/api/payment/requests/webhook` _(replace hostname with your API public url)_ + - **Event types**: `Checkout order approved`, `Checkout order completed` diff --git a/modules/payment/common.props b/modules/payment/common.props new file mode 100644 index 0000000..1dc6ca8 --- /dev/null +++ b/modules/payment/common.props @@ -0,0 +1,15 @@ + + + latest + 0.1.0 + $(NoWarn);CS1591 + module + + + + + $(NoWarn);0436 + + + + \ No newline at end of file diff --git a/modules/payment/configureawait.props b/modules/payment/configureawait.props new file mode 100644 index 0000000..ed1c953 --- /dev/null +++ b/modules/payment/configureawait.props @@ -0,0 +1,9 @@ + + + + + All + runtime; build; native; contentfiles; analyzers + + + diff --git a/modules/payment/global.json b/modules/payment/global.json new file mode 100644 index 0000000..2b7d34d --- /dev/null +++ b/modules/payment/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "6.0", + "rollForward": "latestFeature" + } +} diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/FodyWeavers.xml b/modules/payment/src/Payment.Admin.Application.Contracts/FodyWeavers.xml new file mode 100644 index 0000000..be0de3a --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/FodyWeavers.xsd b/modules/payment/src/Payment.Admin.Application.Contracts/FodyWeavers.xsd new file mode 100644 index 0000000..3f3946e --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/Payment.Admin.Application.Contracts.csproj b/modules/payment/src/Payment.Admin.Application.Contracts/Payment.Admin.Application.Contracts.csproj new file mode 100644 index 0000000..d7aca0e --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/Payment.Admin.Application.Contracts.csproj @@ -0,0 +1,17 @@ + + + + + + + netstandard2.0 + Payment.Admin + + + + + + + + + diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/PaymentAdminApplicationContractsModule.cs b/modules/payment/src/Payment.Admin.Application.Contracts/PaymentAdminApplicationContractsModule.cs new file mode 100644 index 0000000..84785b7 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/PaymentAdminApplicationContractsModule.cs @@ -0,0 +1,17 @@ +using System; +using Volo.Abp.Application; +using Volo.Abp.Authorization; +using Volo.Abp.Modularity; + +namespace Payment.Admin +{ + [DependsOn( + typeof(PaymentDomainSharedModule), + typeof(AbpDddApplicationContractsModule), + typeof(AbpAuthorizationModule) + )] + public class PaymentAdminApplicationContractsModule : AbpModule + { + + } +} diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/PaymentAdminRemoteServiceConsts.cs b/modules/payment/src/Payment.Admin.Application.Contracts/PaymentAdminRemoteServiceConsts.cs new file mode 100644 index 0000000..ba37b75 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/PaymentAdminRemoteServiceConsts.cs @@ -0,0 +1,7 @@ +namespace Payment.Admin +{ + public class PaymentAdminRemoteServiceConsts + { + public const string RemoteServiceName = "PaymentAdmin"; + } +} diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/Payments/IPaymentRequestAdminAppService.cs b/modules/payment/src/Payment.Admin.Application.Contracts/Payments/IPaymentRequestAdminAppService.cs new file mode 100644 index 0000000..0e139ce --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/Payments/IPaymentRequestAdminAppService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Payment.Admin.Payments +{ + public interface IPaymentRequestAdminAppService : IApplicationService + { + Task> GetListAsync(PaymentRequestGetListInput input); + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/Payments/PaymentRequestGetListInput.cs b/modules/payment/src/Payment.Admin.Application.Contracts/Payments/PaymentRequestGetListInput.cs new file mode 100644 index 0000000..55359cb --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/Payments/PaymentRequestGetListInput.cs @@ -0,0 +1,17 @@ +using System; +using Payment.PaymentRequests; +using Volo.Abp.Application.Dtos; + +namespace Payment.Admin.Payments +{ + public class PaymentRequestGetListInput : PagedAndSortedResultRequestDto + { + public string ProductName { get; set; } + + public DateTime? MaxCreationTime { get; set; } + + public DateTime? MinCreationTime { get; set; } + + public PaymentRequestState? Status { get; set; } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/Payments/PaymentRequestWithDetailsDto.cs b/modules/payment/src/Payment.Admin.Application.Contracts/Payments/PaymentRequestWithDetailsDto.cs new file mode 100644 index 0000000..9c127ba --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/Payments/PaymentRequestWithDetailsDto.cs @@ -0,0 +1,24 @@ +using System; +using Payment.PaymentRequests; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; + +namespace Payment.Admin.Payments +{ + public class PaymentRequestWithDetailsDto : ExtensibleEntityDto, IHasCreationTime + { + public string CustomerId { get; set; } + + public string ProductName { get; set; } + + public float Price { get; set; } + + public string Currency { get; set; } + + public PaymentRequestState State { get; set; } + + public string FailReason { get; set; } + + public DateTime CreationTime { get; } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/Permissions/PaymentAdminPermissionDefinitionProvider.cs b/modules/payment/src/Payment.Admin.Application.Contracts/Permissions/PaymentAdminPermissionDefinitionProvider.cs new file mode 100644 index 0000000..7c4ba03 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/Permissions/PaymentAdminPermissionDefinitionProvider.cs @@ -0,0 +1,20 @@ +using Payment.Localization; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; + +namespace Payment.Admin.Permissions +{ + public class PaymentAdminPermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var paymentGroup = context.AddGroup(PaymentAdminPermissions.GroupName, L("Permission:PaymentManagement")); + paymentGroup.AddPermission(PaymentAdminPermissions.Request.Default, L("Permission:PaymentRequest")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application.Contracts/Permissions/PaymentAdminPermissions.cs b/modules/payment/src/Payment.Admin.Application.Contracts/Permissions/PaymentAdminPermissions.cs new file mode 100644 index 0000000..3328e55 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application.Contracts/Permissions/PaymentAdminPermissions.cs @@ -0,0 +1,12 @@ +namespace Payment.Admin.Permissions +{ + public class PaymentAdminPermissions + { + public const string GroupName = "PaymentAdmin"; + + public static class Request + { + public const string Default = GroupName + ".Request"; + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application/FodyWeavers.xml b/modules/payment/src/Payment.Admin.Application/FodyWeavers.xml new file mode 100644 index 0000000..be0de3a --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application/FodyWeavers.xsd b/modules/payment/src/Payment.Admin.Application/FodyWeavers.xsd new file mode 100644 index 0000000..3f3946e --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application/Payment.Admin.Application.csproj b/modules/payment/src/Payment.Admin.Application/Payment.Admin.Application.csproj new file mode 100644 index 0000000..2303f22 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application/Payment.Admin.Application.csproj @@ -0,0 +1,17 @@ + + + + + + + netstandard2.0 + Payment.Admin + + + + + + + + + diff --git a/modules/payment/src/Payment.Admin.Application/PaymentAdminApplicationAutoMapperProfile.cs b/modules/payment/src/Payment.Admin.Application/PaymentAdminApplicationAutoMapperProfile.cs new file mode 100644 index 0000000..605e3dd --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application/PaymentAdminApplicationAutoMapperProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using Payment.Admin.Payments; +using Payment.PaymentRequests; + +namespace Payment.Admin +{ + public class PaymentAdminApplicationAutoMapperProfile : Profile + { + public PaymentAdminApplicationAutoMapperProfile() + { + CreateMap(); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Application/PaymentAdminApplicationModule.cs b/modules/payment/src/Payment.Admin.Application/PaymentAdminApplicationModule.cs new file mode 100644 index 0000000..768e45b --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application/PaymentAdminApplicationModule.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Application; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; + +namespace Payment.Admin +{ + [DependsOn( + typeof(PaymentDomainModule), + typeof(PaymentAdminApplicationContractsModule), + typeof(AbpDddApplicationModule), + typeof(AbpAutoMapperModule) + )] + public class PaymentAdminApplicationModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAutoMapperObjectMapper(); + Configure(options => + { + options.AddProfile(validate: true); + }); + } + } +} diff --git a/modules/payment/src/Payment.Admin.Application/Payments/PaymentRequestAdminAppService.cs b/modules/payment/src/Payment.Admin.Application/Payments/PaymentRequestAdminAppService.cs new file mode 100644 index 0000000..07a8c4e --- /dev/null +++ b/modules/payment/src/Payment.Admin.Application/Payments/PaymentRequestAdminAppService.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Payment.Admin.Permissions; +using Payment.PaymentRequests; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Payment.Admin.Payments +{ + [Authorize(PaymentAdminPermissions.Request.Default)] + public class PaymentRequestAdminAppService : ApplicationService, IPaymentRequestAdminAppService + { + protected IPaymentRequestRepository PaymentRequestRepository { get; } + + public PaymentRequestAdminAppService(IPaymentRequestRepository paymentRequestRepository) + { + PaymentRequestRepository = paymentRequestRepository; + } + + public async Task> GetListAsync(PaymentRequestGetListInput input) + { + var paymentRequests = await PaymentRequestRepository.GetListAsync( + input.SkipCount, + input.MaxResultCount, + input.Sorting, + input.ProductName, + input.MaxCreationTime, + input.MinCreationTime, + input.Status + ); + + var totalCount = await PaymentRequestRepository.GetCountAsync( + input.ProductName, + input.MaxCreationTime, + input.MinCreationTime, + input.Status + ); + + return new PagedResultDto( + totalCount, + ObjectMapper.Map, List>(paymentRequests) + ); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/FodyWeavers.xml b/modules/payment/src/Payment.Admin.Blazor/FodyWeavers.xml new file mode 100644 index 0000000..be0de3a --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/FodyWeavers.xsd b/modules/payment/src/Payment.Admin.Blazor/FodyWeavers.xsd new file mode 100644 index 0000000..3f3946e --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/Navigation/PaymentAdminBlazorMenuContributor.cs b/modules/payment/src/Payment.Admin.Blazor/Navigation/PaymentAdminBlazorMenuContributor.cs new file mode 100644 index 0000000..70c0c25 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/Navigation/PaymentAdminBlazorMenuContributor.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using Payment.Admin.Permissions; +using Payment.Localization; +using Volo.Abp.UI.Navigation; + +namespace Payment.Admin.Navigation +{ + public class PaymentAdminBlazorMenuContributor : IMenuContributor + { + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + await ConfigureMainMenuAsync(context); + } + } + + private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + await AddPaymentMenuAsync(context); + } + + private Task AddPaymentMenuAsync(MenuConfigurationContext context) + { + var l = context.GetLocalizer(); + + var paymentMenu = context.Menu.FindMenuItem(PaymentAdminMenuNames.GroupName); + if (paymentMenu == null) + { + paymentMenu = new ApplicationMenuItem( + PaymentAdminMenuNames.GroupName, + l["Menu:PaymentManagement"], + icon: "fa fa-money-check", + url: "/payment/requests", + requiredPermissionName: PaymentAdminPermissions.Request.Default + ); + } + + context.Menu.AddItem(paymentMenu); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/Navigation/PaymentAdminMenuNames.cs b/modules/payment/src/Payment.Admin.Blazor/Navigation/PaymentAdminMenuNames.cs new file mode 100644 index 0000000..db517fa --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/Navigation/PaymentAdminMenuNames.cs @@ -0,0 +1,7 @@ +namespace Payment.Admin.Navigation +{ + public static class PaymentAdminMenuNames + { + public const string GroupName = "Payment"; + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/Pages/Payment/Requests/Index.razor b/modules/payment/src/Payment.Admin.Blazor/Pages/Payment/Requests/Index.razor new file mode 100644 index 0000000..4075632 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/Pages/Payment/Requests/Index.razor @@ -0,0 +1,89 @@ +@page "/Payment/Requests" +@using Microsoft.AspNetCore.Authorization +@attribute [Authorize(PaymentAdminPermissions.Request.Default)] +@using global::Payment.Admin.Payments +@using global::Payment.PaymentRequests +@using global::Payment.Admin.Permissions +@using Volo.Abp.BlazoriseUI.Components.ObjectExtending +@inherits PaymentAdminComponentBase + + + + @L["PaymentRequests"] + + + + + + + @L["ProductName"] + + + + @L["Status"] + + - + @foreach (var item in PaymentRequestStates) + { + @L["Enum:PaymentRequestState:" + item] + } + + + + @L["MinCreationTime"] + + + + @L["MaxCreationTime"] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/Pages/Payment/Requests/Index.razor.cs b/modules/payment/src/Payment.Admin.Blazor/Pages/Payment/Requests/Index.razor.cs new file mode 100644 index 0000000..b30ef9d --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/Pages/Payment/Requests/Index.razor.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazorise; +using Blazorise.DataGrid; +using Microsoft.AspNetCore.Components; +using Payment.Admin.Payments; +using Volo.Abp.Application.Dtos; +using Payment.PaymentRequests; + +namespace Payment.Admin.Pages.Payment.Requests +{ + public partial class Index + { + [Inject] + protected IPaymentRequestAdminAppService PaymentRequestAdminAppService { get; set; } + + protected IReadOnlyList PaymentRequests { get; set; } + + protected IReadOnlyList PaymentRequestStates => Enum.GetValues(); + + protected PaymentRequestGetListInput GetListInput { get; set; } + + protected virtual int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount; + + protected int CurrentPage { get; set; } = 1; + + protected string CurrentSorting { get; set; } + + protected int? TotalCount { get; set; } + + public Index() + { + GetListInput = new(); + } + + protected override async Task OnInitializedAsync() + { + await GetPaymentRequestsAsync(); + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task GetPaymentRequestsAsync() + { + try + { + GetListInput.Sorting = CurrentSorting; + GetListInput.SkipCount = (CurrentPage - 1) * PageSize; + GetListInput.MaxResultCount = PageSize; + + var result = await PaymentRequestAdminAppService.GetListAsync(GetListInput); + PaymentRequests = result.Items.As>(); + TotalCount = (int?) result.TotalCount; + await InvokeAsync(StateHasChanged); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual async Task OnDataGridReadAsync(DataGridReadDataEventArgs e) + { + CurrentSorting = e.Columns + .Where(c => c.SortDirection != SortDirection.None) + .Select(c => c.Field + (c.SortDirection == SortDirection.Descending ? " DESC" : "")) + .JoinAsString(","); + + CurrentPage = e.Page; + + await GetPaymentRequestsAsync(); + await InvokeAsync(StateHasChanged); + } + + private async Task OnPaymentRequestStateChanged(PaymentRequestState? status) + { + GetListInput.Status = status; + await GetPaymentRequestsAsync(); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/Payment.Admin.Blazor.csproj b/modules/payment/src/Payment.Admin.Blazor/Payment.Admin.Blazor.csproj new file mode 100644 index 0000000..75eaf12 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/Payment.Admin.Blazor.csproj @@ -0,0 +1,16 @@ + + + + + + + net6.0 + Payment.Admin + + + + + + + + diff --git a/modules/payment/src/Payment.Admin.Blazor/PaymentAdminBlazorModule.cs b/modules/payment/src/Payment.Admin.Blazor/PaymentAdminBlazorModule.cs new file mode 100644 index 0000000..dd59606 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/PaymentAdminBlazorModule.cs @@ -0,0 +1,28 @@ +using Payment.Admin.Navigation; +using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; +using Volo.Abp.Modularity; +using Volo.Abp.UI.Navigation; + +namespace Payment.Admin +{ + [DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingModule), + typeof(PaymentAdminHttpApiClientModule) + )] + public class PaymentAdminBlazorModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.MenuContributors.Add(new PaymentAdminBlazorMenuContributor()); + }); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(PaymentAdminBlazorModule).Assembly); + }); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/PaymentAdminComponentBase.cs b/modules/payment/src/Payment.Admin.Blazor/PaymentAdminComponentBase.cs new file mode 100644 index 0000000..7208716 --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/PaymentAdminComponentBase.cs @@ -0,0 +1,14 @@ +using Payment.Localization; +using Volo.Abp.AspNetCore.Components; + +namespace Payment.Admin +{ + public abstract class PaymentAdminComponentBase : AbpComponentBase + { + protected PaymentAdminComponentBase() + { + LocalizationResource = typeof(PaymentResource); + ObjectMapperContext = typeof(PaymentAdminBlazorModule); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.Blazor/_Imports.razor b/modules/payment/src/Payment.Admin.Blazor/_Imports.razor new file mode 100644 index 0000000..3faf16f --- /dev/null +++ b/modules/payment/src/Payment.Admin.Blazor/_Imports.razor @@ -0,0 +1,6 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web +@using Volo.Abp.BlazoriseUI +@using Volo.Abp.BlazoriseUI.Components +@using Blazorise +@using Blazorise.DataGrid \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/PaymentRequestAdminClientProxy.Generated.cs b/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/PaymentRequestAdminClientProxy.Generated.cs new file mode 100644 index 0000000..83c3d7c --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/PaymentRequestAdminClientProxy.Generated.cs @@ -0,0 +1,26 @@ +// This file is automatically generated by ABP framework to use MVC Controllers from CSharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Http.Client; +using Volo.Abp.Http.Modeling; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using Payment.Admin.Payments; + +// ReSharper disable once CheckNamespace +namespace Payment.Admin.Payments.ClientProxies +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(IPaymentRequestAdminAppService), typeof(PaymentRequestAdminClientProxy))] + public partial class PaymentRequestAdminClientProxy : ClientProxyBase, IPaymentRequestAdminAppService + { + public virtual async Task> GetListAsync(PaymentRequestGetListInput input) + { + return await RequestAsync>(nameof(GetListAsync), new ClientProxyRequestTypeValue + { + { typeof(PaymentRequestGetListInput), input } + }); + } + } +} diff --git a/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/PaymentRequestAdminClientProxy.cs b/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/PaymentRequestAdminClientProxy.cs new file mode 100644 index 0000000..21226e5 --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/PaymentRequestAdminClientProxy.cs @@ -0,0 +1,8 @@ +// This file is part of PaymentRequestAdminClientProxy, you can customize it here +// ReSharper disable once CheckNamespace +namespace Payment.Admin.Payments.ClientProxies +{ + public partial class PaymentRequestAdminClientProxy + { + } +} diff --git a/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/paymentAdmin-generate-proxy.json b/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/paymentAdmin-generate-proxy.json new file mode 100644 index 0000000..238a662 --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi.Client/ClientProxies/paymentAdmin-generate-proxy.json @@ -0,0 +1,132 @@ +{ + "modules": { + "paymentAdmin": { + "rootPath": "paymentAdmin", + "remoteServiceName": "PaymentAdmin", + "controllers": { + "Payment.Admin.Payments.PaymentRequestAdminController": { + "controllerName": "PaymentRequestAdmin", + "controllerGroupName": "PaymentRequestAdmin", + "type": "Payment.Admin.Payments.PaymentRequestAdminController", + "interfaces": [ + { + "type": "Payment.Admin.Payments.IPaymentRequestAdminAppService" + } + ], + "actions": { + "GetListAsyncByInput": { + "uniqueName": "GetListAsyncByInput", + "name": "GetListAsync", + "httpMethod": "GET", + "url": "api/payment/requests", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "input", + "typeAsString": "Payment.Admin.Payments.PaymentRequestGetListInput, Payment.Admin.Application.Contracts", + "type": "Payment.Admin.Payments.PaymentRequestGetListInput", + "typeSimple": "Payment.Admin.Payments.PaymentRequestGetListInput", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "input", + "name": "ProductName", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "MaxCreationTime", + "jsonName": null, + "type": "System.DateTime?", + "typeSimple": "string?", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "MinCreationTime", + "jsonName": null, + "type": "System.DateTime?", + "typeSimple": "string?", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "Status", + "jsonName": null, + "type": "Payment.PaymentRequests.PaymentRequestState?", + "typeSimple": "Payment.PaymentRequests.PaymentRequestState?", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "Sorting", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "SkipCount", + "jsonName": null, + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, + { + "nameOnMethod": "input", + "name": "MaxResultCount", + "jsonName": null, + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.PagedResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.PagedResultDto" + }, + "allowAnonymous": null, + "implementFrom": "Payment.Admin.Payments.IPaymentRequestAdminAppService" + } + } + } + } + } + }, + "types": {} +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.HttpApi.Client/FodyWeavers.xml b/modules/payment/src/Payment.Admin.HttpApi.Client/FodyWeavers.xml new file mode 100644 index 0000000..be0de3a --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi.Client/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.HttpApi.Client/FodyWeavers.xsd b/modules/payment/src/Payment.Admin.HttpApi.Client/FodyWeavers.xsd new file mode 100644 index 0000000..3f3946e --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi.Client/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.HttpApi.Client/Payment.Admin.HttpApi.Client.csproj b/modules/payment/src/Payment.Admin.HttpApi.Client/Payment.Admin.HttpApi.Client.csproj new file mode 100644 index 0000000..a58a2f8 --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi.Client/Payment.Admin.HttpApi.Client.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + Payment.Admin + + + + + + + + + + + + + diff --git a/modules/payment/src/Payment.Admin.HttpApi.Client/PaymentAdminHttpApiClientModule.cs b/modules/payment/src/Payment.Admin.HttpApi.Client/PaymentAdminHttpApiClientModule.cs new file mode 100644 index 0000000..a363a83 --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi.Client/PaymentAdminHttpApiClientModule.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace Payment.Admin +{ + [DependsOn( + typeof(PaymentAdminApplicationContractsModule), + typeof(AbpHttpClientModule) + )] + public class PaymentAdminHttpApiClientModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddStaticHttpClientProxies( + typeof(PaymentAdminApplicationContractsModule).Assembly, + PaymentAdminRemoteServiceConsts.RemoteServiceName + ); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + } + } +} diff --git a/modules/payment/src/Payment.Admin.HttpApi/FodyWeavers.xml b/modules/payment/src/Payment.Admin.HttpApi/FodyWeavers.xml new file mode 100644 index 0000000..be0de3a --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.HttpApi/FodyWeavers.xsd b/modules/payment/src/Payment.Admin.HttpApi/FodyWeavers.xsd new file mode 100644 index 0000000..3f3946e --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.HttpApi/Payment.Admin.HttpApi.csproj b/modules/payment/src/Payment.Admin.HttpApi/Payment.Admin.HttpApi.csproj new file mode 100644 index 0000000..01c0964 --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi/Payment.Admin.HttpApi.csproj @@ -0,0 +1,16 @@ + + + + + + + net6.0 + Payment.Admin + + + + + + + + diff --git a/modules/payment/src/Payment.Admin.HttpApi/PaymentAdminController.cs b/modules/payment/src/Payment.Admin.HttpApi/PaymentAdminController.cs new file mode 100644 index 0000000..dd415b3 --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi/PaymentAdminController.cs @@ -0,0 +1,13 @@ +using Payment.Localization; +using Volo.Abp.AspNetCore.Mvc; + +namespace Payment.Admin +{ + public abstract class PaymentAdminController : AbpControllerBase + { + protected PaymentAdminController() + { + LocalizationResource = typeof(PaymentResource); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Admin.HttpApi/PaymentAdminHttpApiModule.cs b/modules/payment/src/Payment.Admin.HttpApi/PaymentAdminHttpApiModule.cs new file mode 100644 index 0000000..a7b9868 --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi/PaymentAdminHttpApiModule.cs @@ -0,0 +1,34 @@ +using Localization.Resources.AbpUi; +using Microsoft.Extensions.DependencyInjection; +using Payment.Localization; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace Payment.Admin +{ + [DependsOn( + typeof(PaymentAdminApplicationContractsModule), + typeof(AbpAspNetCoreMvcModule) + )] + public class PaymentAdminHttpApiModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(PaymentAdminHttpApiModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpUiResource)); + }); + } + } +} diff --git a/modules/payment/src/Payment.Admin.HttpApi/Payments/PaymentRequestAdminController.cs b/modules/payment/src/Payment.Admin.HttpApi/Payments/PaymentRequestAdminController.cs new file mode 100644 index 0000000..311b8b0 --- /dev/null +++ b/modules/payment/src/Payment.Admin.HttpApi/Payments/PaymentRequestAdminController.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.Application.Dtos; + +namespace Payment.Admin.Payments +{ + [RemoteService(Name = PaymentAdminRemoteServiceConsts.RemoteServiceName)] + [Area("paymentAdmin")] + [Route("api/payment/requests")] + public class PaymentRequestAdminController : PaymentAdminController, IPaymentRequestAdminAppService + { + protected IPaymentRequestAdminAppService PaymentRequestAdminAppService { get; } + + public PaymentRequestAdminController(IPaymentRequestAdminAppService paymentRequestAdminAppService) + { + PaymentRequestAdminAppService = paymentRequestAdminAppService; + } + + [HttpGet] + public Task> GetListAsync(PaymentRequestGetListInput input) + { + return PaymentRequestAdminAppService.GetListAsync(input); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/FodyWeavers.xml b/modules/payment/src/Payment.Application.Contracts/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/FodyWeavers.xsd b/modules/payment/src/Payment.Application.Contracts/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/Payment.Application.Contracts.csproj b/modules/payment/src/Payment.Application.Contracts/Payment.Application.Contracts.csproj new file mode 100644 index 0000000..0af2468 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/Payment.Application.Contracts.csproj @@ -0,0 +1,17 @@ + + + + + + + netstandard2.0 + Payment + + + + + + + + + diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentApplicationContractsModule.cs b/modules/payment/src/Payment.Application.Contracts/PaymentApplicationContractsModule.cs new file mode 100644 index 0000000..5db0482 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/PaymentApplicationContractsModule.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Application; +using Volo.Abp.Modularity; +using Volo.Abp.Authorization; + +namespace Payment +{ + [DependsOn( + typeof(PaymentDomainSharedModule), + typeof(AbpDddApplicationContractsModule), + typeof(AbpAuthorizationModule) + )] + public class PaymentApplicationContractsModule : AbpModule + { + + } +} diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRemoteServiceConsts.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRemoteServiceConsts.cs new file mode 100644 index 0000000..fc99a3c --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRemoteServiceConsts.cs @@ -0,0 +1,7 @@ +namespace Payment +{ + public class PaymentRemoteServiceConsts + { + public const string RemoteServiceName = "Payment"; + } +} diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs new file mode 100644 index 0000000..05fd627 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace Payment.PaymentRequests +{ + public interface IPaymentRequestAppService : IApplicationService + { + Task GetAsync(Guid id); + + Task CreateAsync(PaymentRequestCreationDto input); + + Task StartPaymentAsync(StartPaymentDto input); + + Task CompleteAsync(string token); + + Task HandleWebhookAsync(string payload); + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs new file mode 100644 index 0000000..d8f09e8 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestCreationDto.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.ObjectExtending; +using Volo.Abp.Validation; + +namespace Payment.PaymentRequests +{ + [Serializable] + public class PaymentRequestCreationDto : ExtensibleObject + { + [DynamicMaxLength(typeof(PaymentRequestConsts), nameof(PaymentRequestConsts.MaxCustomerIdLength))] + public string CustomerId { get; set; } + + [DynamicMaxLength(typeof(PaymentRequestConsts), nameof(PaymentRequestConsts.MaxProductIdLength))] + public string ProductId { get; set; } + + [Required] + [DynamicMaxLength(typeof(PaymentRequestConsts), nameof(PaymentRequestConsts.MaxProductNameLength))] + public string ProductName { get; set; } + + public decimal Price { get; set; } + + /// + /// 3 Letter Language Code in ISO 4217 Standards. + /// For example: USD, EUR. + /// + [StringLength(PaymentRequestConsts.MaxCurrencyLength)] + [CanBeNull] + public string Currency { get; set; } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs new file mode 100644 index 0000000..a1f6689 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/PaymentRequestDto.cs @@ -0,0 +1,29 @@ +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Data; + +namespace Payment.PaymentRequests +{ + [Serializable] + public class PaymentRequestDto : CreationAuditedEntityDto, IHasExtraProperties + { + public string CustomerId { get; set; } + + public string ProductId { get; set; } + + public string ProductName { get; set; } + + public decimal Price { get; set; } + + public string Currency { get; set; } + + public PaymentRequestState State { get; set; } + + public ExtraPropertyDictionary ExtraProperties { get; set; } + + public PaymentRequestDto() + { + ExtraProperties = new ExtraPropertyDictionary(); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentDto.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentDto.cs new file mode 100644 index 0000000..be9ca14 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentDto.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using System; +using System.ComponentModel.DataAnnotations; + +namespace Payment.PaymentRequests +{ + [Serializable] + public class StartPaymentDto + { + public Guid PaymentRequestId { get; set; } + + [NotNull] + [Required] + public string ReturnUrl { get; set; } + + public string CancelUrl { get; set; } + } +} diff --git a/modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentResultDto.cs b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentResultDto.cs new file mode 100644 index 0000000..3cf0d4f --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/PaymentRequests/StartPaymentResultDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace Payment.PaymentRequests +{ + [Serializable] + public class StartPaymentResultDto + { + public string CheckoutLink { get; set; } + } +} diff --git a/modules/payment/src/Payment.Application.Contracts/Permissions/PaymentPermissionDefinitionProvider.cs b/modules/payment/src/Payment.Application.Contracts/Permissions/PaymentPermissionDefinitionProvider.cs new file mode 100644 index 0000000..a028087 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/Permissions/PaymentPermissionDefinitionProvider.cs @@ -0,0 +1,19 @@ +using Payment.Localization; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; + +namespace Payment.Permissions +{ + public class PaymentPermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var myGroup = context.AddGroup(PaymentPermissions.GroupName, L("Permission:Payment")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/Permissions/PaymentPermissions.cs b/modules/payment/src/Payment.Application.Contracts/Permissions/PaymentPermissions.cs new file mode 100644 index 0000000..4d9f6b7 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/Permissions/PaymentPermissions.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Reflection; + +namespace Payment.Permissions +{ + public class PaymentPermissions + { + public const string GroupName = "Payment"; + + public static string[] GetAll() + { + return ReflectionHelper.GetPublicConstantsRecursively(typeof(PaymentPermissions)); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application.Contracts/Samples/ISampleAppService.cs b/modules/payment/src/Payment.Application.Contracts/Samples/ISampleAppService.cs new file mode 100644 index 0000000..7b04320 --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/Samples/ISampleAppService.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace Payment.Samples +{ + public interface ISampleAppService : IApplicationService + { + Task GetAsync(); + + Task GetAuthorizedAsync(); + } +} diff --git a/modules/payment/src/Payment.Application.Contracts/Samples/SampleDto.cs b/modules/payment/src/Payment.Application.Contracts/Samples/SampleDto.cs new file mode 100644 index 0000000..d709acf --- /dev/null +++ b/modules/payment/src/Payment.Application.Contracts/Samples/SampleDto.cs @@ -0,0 +1,7 @@ +namespace Payment.Samples +{ + public class SampleDto + { + public int Value { get; set; } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application/FodyWeavers.xml b/modules/payment/src/Payment.Application/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/src/Payment.Application/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Application/FodyWeavers.xsd b/modules/payment/src/Payment.Application/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/src/Payment.Application/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Application/Payment.Application.csproj b/modules/payment/src/Payment.Application/Payment.Application.csproj new file mode 100644 index 0000000..8cbe5b5 --- /dev/null +++ b/modules/payment/src/Payment.Application/Payment.Application.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + Payment + + + + + + + + + + + + + diff --git a/modules/payment/src/Payment.Application/PaymentAppService.cs b/modules/payment/src/Payment.Application/PaymentAppService.cs new file mode 100644 index 0000000..45ac236 --- /dev/null +++ b/modules/payment/src/Payment.Application/PaymentAppService.cs @@ -0,0 +1,14 @@ +using Payment.Localization; +using Volo.Abp.Application.Services; + +namespace Payment +{ + public abstract class PaymentAppService : ApplicationService + { + protected PaymentAppService() + { + LocalizationResource = typeof(PaymentResource); + ObjectMapperContext = typeof(PaymentApplicationModule); + } + } +} diff --git a/modules/payment/src/Payment.Application/PaymentApplicationAutoMapperProfile.cs b/modules/payment/src/Payment.Application/PaymentApplicationAutoMapperProfile.cs new file mode 100644 index 0000000..bf8c80d --- /dev/null +++ b/modules/payment/src/Payment.Application/PaymentApplicationAutoMapperProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using Payment.PaymentRequests; + +namespace Payment +{ + public class PaymentApplicationAutoMapperProfile : Profile + { + public PaymentApplicationAutoMapperProfile() + { + CreateMap(); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application/PaymentApplicationModule.cs b/modules/payment/src/Payment.Application/PaymentApplicationModule.cs new file mode 100644 index 0000000..b700bc9 --- /dev/null +++ b/modules/payment/src/Payment.Application/PaymentApplicationModule.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; +using Volo.Abp.Application; +using Microsoft.Extensions.Options; +using Payment.PayPal; +using System; +using PayPalCheckoutSdk.Core; + +namespace Payment +{ + [DependsOn( + typeof(PaymentDomainModule), + typeof(PaymentApplicationContractsModule), + typeof(AbpDddApplicationModule), + typeof(AbpAutoMapperModule) + )] + public class PaymentApplicationModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAutoMapperObjectMapper(); + Configure(options => + { + options.AddMaps(validate: true); + }); + + context.Services.AddTransient(provider => + { + var options = provider.GetService>().Value; + + if (options.Environment.IsNullOrWhiteSpace() || options.Environment == PayPalConsts.Environment.Sandbox) + { + return new PayPalHttpClient(new SandboxEnvironment(options.ClientId, options.Secret)); + } + + return new PayPalHttpClient(new LiveEnvironment(options.ClientId, options.Secret)); + }); + } + } +} diff --git a/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs b/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs new file mode 100644 index 0000000..b585580 --- /dev/null +++ b/modules/payment/src/Payment.Application/PaymentRequests/PaymentRequestAppService.cs @@ -0,0 +1,173 @@ +using Microsoft.Extensions.Options; +using Newtonsoft.Json.Linq; +using Payment.PayPal; +using PayPalCheckoutSdk.Core; +using PayPalCheckoutSdk.Orders; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace Payment.PaymentRequests +{ + public class PaymentRequestAppService : PaymentAppService, IPaymentRequestAppService + { + protected readonly IPaymentRequestRepository _paymentRequestRepository; + protected readonly PaymentOptions _paymentOptions; + protected readonly PayPalHttpClient _payPalHttpClient; + + public PaymentRequestAppService( + IPaymentRequestRepository paymentRequestRepository, + IOptions paymentOptions, + PayPalHttpClient payPalHttpClient) + { + _paymentRequestRepository = paymentRequestRepository; + _paymentOptions = paymentOptions.Value; + _payPalHttpClient = payPalHttpClient; + } + + public async Task GetAsync(Guid id) + { + var paymentRequest = await _paymentRequestRepository.GetAsync(id); + + return ObjectMapper.Map(paymentRequest); + } + + public async Task CreateAsync(PaymentRequestCreationDto input) + { + var paymentRequest = new PaymentRequest( + GuidGenerator.Create(), + input.CustomerId, + input.ProductId, + input.ProductName, + input.Price, + input.Currency ?? _paymentOptions.DefaultCurrency + ); + + foreach (var extraProperty in input.ExtraProperties) + { + paymentRequest.SetProperty(extraProperty.Key, extraProperty.Value); + } + + await _paymentRequestRepository.InsertAsync(paymentRequest); + + return ObjectMapper.Map(paymentRequest); + } + + public async Task StartPaymentAsync(StartPaymentDto input) + { + var paymentRequest = await _paymentRequestRepository.GetAsync(input.PaymentRequestId); + + var order = new OrderRequest + { + CheckoutPaymentIntent = "CAPTURE", + ApplicationContext = new ApplicationContext + { + ReturnUrl = input.ReturnUrl, + CancelUrl = input.CancelUrl, + }, + PurchaseUnits = new List + { + new PurchaseUnitRequest + { + AmountWithBreakdown = new AmountWithBreakdown + { + AmountBreakdown = new AmountBreakdown + { + ItemTotal = new Money + { + CurrencyCode = paymentRequest.Currency, + Value = paymentRequest.Price.ToString(".00") + } + }, + CurrencyCode = paymentRequest.Currency, + Value = paymentRequest.Price.ToString(".00"), + }, + Items = new List + { + new Item + { + Quantity = 1.ToString(), + Name = paymentRequest.ProductName, + UnitAmount = new Money + { + CurrencyCode = paymentRequest.Currency, + Value = paymentRequest.Price.ToString(".00") + } + } + }, + ReferenceId = paymentRequest.Id.ToString() + } + } + }; + + var request = new OrdersCreateRequest(); + request.Prefer("return=representation"); + request.RequestBody(order); + + var result = (await _payPalHttpClient.Execute(request)).Result(); + + return new StartPaymentResultDto + { + CheckoutLink = result.Links.First(x => x.Rel == "approve").Href + }; + } + + public async Task CompleteAsync(string token) + { + var request = new OrdersCaptureRequest(token); + request.RequestBody(new OrderActionRequest()); + + var order = (await _payPalHttpClient.Execute(request)).Result(); + + var paymentRequest = await UpdatePaymentRequestStateAsync(order); + + return ObjectMapper.Map(paymentRequest); + } + + public async Task HandleWebhookAsync(string payload) + { + var jObject = JObject.Parse(payload); + + //// TODO: Find way to parse with System.Text.Json instead of Newtonsoft + var order = jObject["resource"].ToObject(); + + var request = new OrdersGetRequest(order.Id); + + // Ensure order object comes from PayPal + var response = await _payPalHttpClient.Execute(request); + order = response.Result(); + + await UpdatePaymentRequestStateAsync(order); + + // PayPal doesn't accept Http 204 (NoContent) result and tries to execute webhook again. + // So with following value, API returns Http 200 (OK) result. + return true; + } + + private async Task UpdatePaymentRequestStateAsync(Order order) + { + var paymentRequestId = Guid.Parse(order.PurchaseUnits.First().ReferenceId); + + var paymentRequest = await _paymentRequestRepository.GetAsync(paymentRequestId); + + if (order.Status is PayPalConsts.OrderStatus.Completed or PayPalConsts.OrderStatus.Approved) + { + paymentRequest.SetAsCompleted(); + } + else + { + paymentRequest.SetAsFailed(order.Status); + } + + paymentRequest.ExtraProperties[PayPalConsts.OrderIdPropertyName] = order.Id; + paymentRequest.ExtraProperties[nameof(order.Status)] = order.Status; + + await _paymentRequestRepository.UpdateAsync(paymentRequest); + + return paymentRequest; + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Application/Samples/SampleAppService.cs b/modules/payment/src/Payment.Application/Samples/SampleAppService.cs new file mode 100644 index 0000000..a59a125 --- /dev/null +++ b/modules/payment/src/Payment.Application/Samples/SampleAppService.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; + +namespace Payment.Samples +{ + public class SampleAppService : PaymentAppService, ISampleAppService + { + public Task GetAsync() + { + return Task.FromResult( + new SampleDto + { + Value = 42 + } + ); + } + + [Authorize] + public Task GetAuthorizedAsync() + { + return Task.FromResult( + new SampleDto + { + Value = 42 + } + ); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml b/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml new file mode 100644 index 0000000..00e1d9a --- /dev/null +++ b/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd b/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd new file mode 100644 index 0000000..3f3946e --- /dev/null +++ b/modules/payment/src/Payment.BackgroundServices/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.BackgroundServices/Payment.BackgroundServices.csproj b/modules/payment/src/Payment.BackgroundServices/Payment.BackgroundServices.csproj new file mode 100644 index 0000000..23e592f --- /dev/null +++ b/modules/payment/src/Payment.BackgroundServices/Payment.BackgroundServices.csproj @@ -0,0 +1,15 @@ + + + + + + + netstandard2.0 + Payment + + + + + + + diff --git a/modules/payment/src/Payment.BackgroundServices/PaymentBackgroundServicesModule.cs b/modules/payment/src/Payment.BackgroundServices/PaymentBackgroundServicesModule.cs new file mode 100644 index 0000000..e64fb55 --- /dev/null +++ b/modules/payment/src/Payment.BackgroundServices/PaymentBackgroundServicesModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Modularity; + +namespace Payment +{ + [DependsOn( + typeof(PaymentDomainModule) + )] + public class PaymentBackgroundServicesModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/FodyWeavers.xml b/modules/payment/src/Payment.Domain.Shared/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/FodyWeavers.xsd b/modules/payment/src/Payment.Domain.Shared/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/ar.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/ar.json new file mode 100644 index 0000000..33f037e --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/ar.json @@ -0,0 +1,7 @@ +{ + "culture": "ar", + "texts": { + "MyAccount": "إدارة ملفى", + "SamplePageMessage": "صفحة نموذجية للوحدة النمطية Payment" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/cs.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/cs.json new file mode 100644 index 0000000..fa44b2f --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/cs.json @@ -0,0 +1,7 @@ +{ + "culture": "cs", + "texts": { + "MyAccount": "Spravovat profil", + "SamplePageMessage": "Ukázková stránka pro modul Payment" + } +} diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/de-DE.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/de-DE.json new file mode 100644 index 0000000..cd06930 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/de-DE.json @@ -0,0 +1,7 @@ +{ + "culture": "de-DE", + "texts": { + "MyAccount": "Mein Konto", + "SamplePageMessage": "Eine Beispielseite für das Modul PaymentModul" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/en-GB.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/en-GB.json new file mode 100644 index 0000000..1c93a89 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/en-GB.json @@ -0,0 +1,7 @@ +{ + "culture": "en-GB", + "texts": { + "MyAccount": "My account", + "SamplePageMessage": "A sample page for the Payment module" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/en.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/en.json new file mode 100644 index 0000000..d94faf4 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/en.json @@ -0,0 +1,36 @@ + +{ + "culture": "en", + "texts": { + "CompletePayment": "Complete the Payment", + "Enum:PaymentRequestState:Completed": "Completed", + "Enum:PaymentRequestState:Failed": "Failed", + "Enum:PaymentRequestState:Waiting": "Waiting", + "GoBack": "Go Back", + "MyAccount": "My account", + "PaymentRequestIsNotWaiting": "The state isn't 'Waiting', so you can't pay this Payment Request at this time.", + "PaymentResult": "Payment Result", + "Price": "Price", + "ProductInformation": "Product Information", + "ProductName": "Product Name", + "SamplePageMessage": "A sample page for the Payment module", + "Status": "State", + "YourPaymentCompletedSuccessfully": "Your payment has been completed successfully!", + "YourPaymentFailed": "Your payment has been failed unfortunetely!", + "Permission:Payment": "Payment", + "Menu:PaymentManagement": "Payment", + "PaymentRequests": "Payment Requests", + "PaymentRequestState:0": "Waiting", + "PaymentRequestState:1": "Completed", + "PaymentRequestState:2": "Failed", + "MinCreationTime": "Min Creation Time", + "MaxCreationTime": "Max Creation Time", + "CustomerId": "Customer Id", + "Currency": "Currency", + "State": "State", + "FailReason": "Fail Reason", + "CreationTime": "Creation Time", + "Permission:PaymentManagement": "Payment Management", + "Permission:PaymentRequest": "Payment Requests" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/es.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/es.json new file mode 100644 index 0000000..c126788 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/es.json @@ -0,0 +1,7 @@ +{ + "culture": "es", + "texts": { + "MyAccount": "Mi cuenta", + "SamplePageMessage": "Una página de ejemplo para el módulo Payment " + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/fi.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/fi.json new file mode 100644 index 0000000..1a52ba1 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/fi.json @@ -0,0 +1,7 @@ +{ + "culture": "fi", + "texts": { + "MyAccount": "Tilini", + "SamplePageMessage": "Esimerkkisivu Payment-moduulille" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/fr.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/fr.json new file mode 100644 index 0000000..3e3b5e2 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/fr.json @@ -0,0 +1,7 @@ +{ + "culture": "fr", + "texts": { + "MyAccount": "Mon compte", + "SamplePageMessage": "Exemple de page pour le module Payment" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/hi.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/hi.json new file mode 100644 index 0000000..29d55be --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/hi.json @@ -0,0 +1,7 @@ +{ + "culture": "hi", + "texts": { + "MyAccount": "मेरा खाता", + "SamplePageMessage": "Payment मॉड्यूल के लिए एक नमूना पृष्ठ" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/hu.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/hu.json new file mode 100644 index 0000000..768db7a --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/hu.json @@ -0,0 +1,7 @@ +{ + "culture": "hu", + "texts": { + "MyAccount": "A fiókom", + "SamplePageMessage": "Mintaoldal a Payment modulhoz" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/it.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/it.json new file mode 100644 index 0000000..20626c6 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/it.json @@ -0,0 +1,7 @@ +{ + "culture": "it", + "texts": { + "MyAccount": "Il mio conto", + "SamplePageMessage": "Una pagina di esempio per il modulo Payment" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/nl.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/nl.json new file mode 100644 index 0000000..14526f1 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/nl.json @@ -0,0 +1,7 @@ +{ + "culture": "nl", + "texts": { + "MyAccount": "Mijn rekening", + "SamplePageMessage": "Een voorbeeldpagina voor de Payment module" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/pl-PL.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/pl-PL.json new file mode 100644 index 0000000..3ea7b19 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/pt-BR.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/pt-BR.json new file mode 100644 index 0000000..6d746df --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/pt-BR.json @@ -0,0 +1,6 @@ +{ + "culture": "pt-BR", + "texts": { + + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/ro-RO.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/ro-RO.json new file mode 100644 index 0000000..6891d68 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/ro-RO.json @@ -0,0 +1,7 @@ +{ + "culture": "ro-RO", + "texts": { + "MyAccount": "Contul meu", + "SamplePageMessage": "Un exemplu de pagină pentru modululul Payment" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/sk.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/sk.json new file mode 100644 index 0000000..161a16a --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/sk.json @@ -0,0 +1,6 @@ +{ + "culture": "sk", + "texts": { + "SamplePageMessage": "Ukážka stránky pre modul Payment" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/sl.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/sl.json new file mode 100644 index 0000000..229c449 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/sl.json @@ -0,0 +1,6 @@ +{ + "culture": "sl", + "texts": { + "MyAccount": "Moj račun" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/tr.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/tr.json new file mode 100644 index 0000000..821d36b --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/tr.json @@ -0,0 +1,7 @@ +{ + "culture": "tr", + "texts": { + "MyAccount": "Hesabım", + "SamplePageMessage": "Payment modulünden örnek bir sayfa" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/vi.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/vi.json new file mode 100644 index 0000000..d8eb5f3 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/vi.json @@ -0,0 +1,6 @@ +{ + "culture": "vi", + "texts": { + + } +} diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/zh-Hans.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/zh-Hans.json new file mode 100644 index 0000000..83bf721 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hans", + "texts": { + "MyAccount": "我的账户", + "SamplePageMessage": "Payment模块的示例页面" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/Payment/zh-Hant.json b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/zh-Hant.json new file mode 100644 index 0000000..699d31e --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/Payment/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hant", + "texts": { + "MyAccount": "我的賬戶" + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/Localization/PaymentResource.cs b/modules/payment/src/Payment.Domain.Shared/Localization/PaymentResource.cs new file mode 100644 index 0000000..2409063 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Localization/PaymentResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Payment.Localization +{ + [LocalizationResourceName("Payment")] + public class PaymentResource + { + + } +} diff --git a/modules/payment/src/Payment.Domain.Shared/PayPal/PayPalConsts.cs b/modules/payment/src/Payment.Domain.Shared/PayPal/PayPalConsts.cs new file mode 100644 index 0000000..51f4374 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/PayPal/PayPalConsts.cs @@ -0,0 +1,47 @@ +namespace Payment.PayPal +{ + public static class PayPalConsts + { + public const string OrderIdPropertyName = "OrderId"; + + public static class OrderStatus + { + /// + /// The order was created with the specified context. + /// + public const string Created = "CREATED"; + + /// + /// The order was saved and persisted. The order status continues to be in progress until a capture is made with final_capture = true for all purchase units within the order. + /// + public const string Saved = "SAVED"; + + /// + /// The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on. + /// + public const string Approved = "APPROVED"; + + /// + /// All purchase units in the order are voided. + /// + public const string Voided = "VOIDED"; + + /// + /// The payment was authorized or the authorized payment was captured for the order. + /// + public const string Completed = "COMPLETED"; + + /// + /// The order requires an action from the payer (e.g. 3DS authentication). Redirect the payer to the "rel":"payer-action" HATEOAS link returned as part of the response prior to authorizing or capturing the order. + /// + public const string PlayerActionRequired = "PAYER_ACTION_REQUIRED"; + } + + public static class Environment + { + public const string Sandbox = "Sandbox"; + + public const string Live = "Live"; + } + } +} diff --git a/modules/payment/src/Payment.Domain.Shared/Payment.Domain.Shared.csproj b/modules/payment/src/Payment.Domain.Shared/Payment.Domain.Shared.csproj new file mode 100644 index 0000000..d4d2f9d --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/Payment.Domain.Shared.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.0 + Payment + true + + + + + + + + + + + + + + + + diff --git a/modules/payment/src/Payment.Domain.Shared/PaymentDomainSharedModule.cs b/modules/payment/src/Payment.Domain.Shared/PaymentDomainSharedModule.cs new file mode 100644 index 0000000..36e5b8d --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/PaymentDomainSharedModule.cs @@ -0,0 +1,37 @@ +using Volo.Abp.Modularity; +using Volo.Abp.Localization; +using Payment.Localization; +using Volo.Abp.Localization.ExceptionHandling; +using Volo.Abp.Validation; +using Volo.Abp.Validation.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace Payment +{ + [DependsOn( + typeof(AbpValidationModule) + )] + public class PaymentDomainSharedModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddBaseTypes(typeof(AbpValidationResource)) + .AddVirtualJson("/Localization/Payment"); + }); + + Configure(options => + { + options.MapCodeNamespace("Payment", typeof(PaymentResource)); + }); + } + } +} diff --git a/modules/payment/src/Payment.Domain.Shared/PaymentErrorCodes.cs b/modules/payment/src/Payment.Domain.Shared/PaymentErrorCodes.cs new file mode 100644 index 0000000..24dff63 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/PaymentErrorCodes.cs @@ -0,0 +1,7 @@ +namespace Payment +{ + public static class PaymentErrorCodes + { + //Add your business exception error codes here... + } +} diff --git a/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestCompletedEto.cs b/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestCompletedEto.cs new file mode 100644 index 0000000..2fc4c8c --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestCompletedEto.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus; + +namespace Payment.PaymentRequests +{ + [Serializable] + [EventName("Payment.Completed")] + public class PaymentRequestCompletedEto : EtoBase, IHasExtraProperties + { + public Guid PaymentRequestId { get; set; } + + public ExtraPropertyDictionary ExtraProperties { get; set; } + } +} diff --git a/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestConsts.cs b/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestConsts.cs new file mode 100644 index 0000000..a4e0690 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestConsts.cs @@ -0,0 +1,10 @@ +namespace Payment.PaymentRequests +{ + public static class PaymentRequestConsts + { + public const int MaxCurrencyLength = 100; + public static int MaxCustomerIdLength { get; set; } = 100; + public static int MaxProductIdLength { get; set; } = 100; + public static int MaxProductNameLength { get; set; } = 200; + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestFailedEto.cs b/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestFailedEto.cs new file mode 100644 index 0000000..5c5fe88 --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestFailedEto.cs @@ -0,0 +1,18 @@ +using System; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus; + +namespace Payment.PaymentRequests +{ + [Serializable] + [EventName("Payment.Failed")] + public class PaymentRequestFailedEto : EtoBase, IHasExtraProperties + { + public Guid PaymentRequestId { get; set; } + + public string FailReason { get; set; } + + public ExtraPropertyDictionary ExtraProperties { get; set; } + } +} diff --git a/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestState.cs b/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestState.cs new file mode 100644 index 0000000..faa3bce --- /dev/null +++ b/modules/payment/src/Payment.Domain.Shared/PaymentRequests/PaymentRequestState.cs @@ -0,0 +1,9 @@ +namespace Payment.PaymentRequests +{ + public enum PaymentRequestState : byte + { + Waiting = 0, + Completed, + Failed + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain/FodyWeavers.xml b/modules/payment/src/Payment.Domain/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/src/Payment.Domain/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain/FodyWeavers.xsd b/modules/payment/src/Payment.Domain/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/src/Payment.Domain/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs b/modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs new file mode 100644 index 0000000..1511ba0 --- /dev/null +++ b/modules/payment/src/Payment.Domain/PayPal/PayPalOptions.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Payment.PayPal +{ + public class PayPalOptions + { + public string ClientId { get; set; } + + public string Secret { get; set; } + + public string Locale { get; set; } + + /// + /// "Sandbox" or "Live". Default value is "Sandbox" + /// + public string Environment { get; set; } = PayPalConsts.Environment.Sandbox; + + public bool Recommended { get; set; } + + public List ExtraInfos { get; set; } = new(); + } +} diff --git a/modules/payment/src/Payment.Domain/Payment.Domain.csproj b/modules/payment/src/Payment.Domain/Payment.Domain.csproj new file mode 100644 index 0000000..9775fdc --- /dev/null +++ b/modules/payment/src/Payment.Domain/Payment.Domain.csproj @@ -0,0 +1,16 @@ + + + + + + + netstandard2.0 + Payment + + + + + + + + diff --git a/modules/payment/src/Payment.Domain/PaymentDbProperties.cs b/modules/payment/src/Payment.Domain/PaymentDbProperties.cs new file mode 100644 index 0000000..ae502eb --- /dev/null +++ b/modules/payment/src/Payment.Domain/PaymentDbProperties.cs @@ -0,0 +1,11 @@ +namespace Payment +{ + public static class PaymentDbProperties + { + public static string DbTablePrefix { get; set; } = "Pay"; + + public static string DbSchema { get; set; } = null; + + public const string ConnectionStringName = "Payment"; + } +} diff --git a/modules/payment/src/Payment.Domain/PaymentDomainModule.cs b/modules/payment/src/Payment.Domain/PaymentDomainModule.cs new file mode 100644 index 0000000..4b76dba --- /dev/null +++ b/modules/payment/src/Payment.Domain/PaymentDomainModule.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using Payment.PayPal; +using Volo.Abp.Domain; +using Volo.Abp.Modularity; + +namespace Payment +{ + [DependsOn( + typeof(AbpDddDomainModule), + typeof(PaymentDomainSharedModule) + )] + public class PaymentDomainModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + Configure(configuration.GetSection("Payment:PayPal")); + Configure(configuration.GetSection("Payment")); + } + } +} diff --git a/modules/payment/src/Payment.Domain/PaymentOptions.cs b/modules/payment/src/Payment.Domain/PaymentOptions.cs new file mode 100644 index 0000000..01f8127 --- /dev/null +++ b/modules/payment/src/Payment.Domain/PaymentOptions.cs @@ -0,0 +1,7 @@ +namespace Payment +{ + public class PaymentOptions + { + public string DefaultCurrency { get; set; } = "USD"; + } +} diff --git a/modules/payment/src/Payment.Domain/PaymentRequests/IPaymentRequestRepository.cs b/modules/payment/src/Payment.Domain/PaymentRequests/IPaymentRequestRepository.cs new file mode 100644 index 0000000..97ffef4 --- /dev/null +++ b/modules/payment/src/Payment.Domain/PaymentRequests/IPaymentRequestRepository.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Payment.PaymentRequests +{ + public interface IPaymentRequestRepository : IRepository + { + Task> GetListAsync( + int skipCount, + int maxResultCount, + string sorting, + string productName = null, + DateTime? maxCreationTime = null, + DateTime? minCreationTime = null, + PaymentRequestState? state = null, + CancellationToken cancellationToken = default + ); + + Task GetCountAsync( + string productName = null, + DateTime? maxCreationTime = null, + DateTime? minCreationTime = null, + PaymentRequestState? state = null, + CancellationToken cancellationToken = default + ); + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs b/modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs new file mode 100644 index 0000000..48fe239 --- /dev/null +++ b/modules/payment/src/Payment.Domain/PaymentRequests/PaymentRequest.cs @@ -0,0 +1,87 @@ +using System; +using System.Linq; +using JetBrains.Annotations; +using Volo.Abp; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Payment.PaymentRequests +{ + public class PaymentRequest : CreationAuditedAggregateRoot, ISoftDelete + { + [CanBeNull] + public string CustomerId { get; protected set; } + + [CanBeNull] + public string ProductId { get; protected set; } + + [NotNull] + public string ProductName { get; protected set; } + + public decimal Price { get; protected set; } + + public string Currency { get; protected set; } + + public PaymentRequestState State { get; protected set; } + + public bool IsDeleted { get; set; } + + [CanBeNull] + public string FailReason { get; protected set; } + + private PaymentRequest() + { + + } + + public PaymentRequest( + Guid id, + [CanBeNull] string customerId, + [CanBeNull] string productId, + [NotNull] string productName, + decimal price, + [NotNull] string currency) + : base(id) + { + CustomerId = Check.NotNullOrEmpty(customerId, nameof(customerId), maxLength: PaymentRequestConsts.MaxCustomerIdLength); + ProductId = Check.NotNullOrEmpty(productId, nameof(productId), maxLength: PaymentRequestConsts.MaxProductIdLength); + ProductName = Check.NotNullOrWhiteSpace(productName, nameof(productName), maxLength: PaymentRequestConsts.MaxProductNameLength); + Price = price; + Currency = currency; + } + + public virtual void SetAsCompleted() + { + if (State == PaymentRequestState.Completed) + { + return; + } + + State = PaymentRequestState.Completed; + FailReason = null; + + AddDistributedEvent(new PaymentRequestCompletedEto + { + PaymentRequestId = Id, + ExtraProperties = ExtraProperties + }); + } + + public virtual void SetAsFailed(string failReason) + { + if (State == PaymentRequestState.Failed) + { + return; + } + + State = PaymentRequestState.Failed; + FailReason = failReason; + + AddDistributedEvent(new PaymentRequestFailedEto + { + PaymentRequestId = Id, + FailReason = failReason, + ExtraProperties = ExtraProperties + }); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain/Settings/PaymentSettingDefinitionProvider.cs b/modules/payment/src/Payment.Domain/Settings/PaymentSettingDefinitionProvider.cs new file mode 100644 index 0000000..de483ad --- /dev/null +++ b/modules/payment/src/Payment.Domain/Settings/PaymentSettingDefinitionProvider.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Settings; + +namespace Payment.Settings +{ + public class PaymentSettingDefinitionProvider : SettingDefinitionProvider + { + public override void Define(ISettingDefinitionContext context) + { + /* Define module settings here. + * Use names from PaymentSettings class. + */ + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Domain/Settings/PaymentSettings.cs b/modules/payment/src/Payment.Domain/Settings/PaymentSettings.cs new file mode 100644 index 0000000..51b216b --- /dev/null +++ b/modules/payment/src/Payment.Domain/Settings/PaymentSettings.cs @@ -0,0 +1,11 @@ +namespace Payment.Settings +{ + public static class PaymentSettings + { + public const string GroupName = "Payment"; + + /* Add constants for setting names. Example: + * public const string MySettingName = GroupName + ".MySettingName"; + */ + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/IPaymentDbContext.cs b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/IPaymentDbContext.cs new file mode 100644 index 0000000..28e4b20 --- /dev/null +++ b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/IPaymentDbContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; +using Payment.PaymentRequests; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace Payment.EntityFrameworkCore +{ + [ConnectionStringName(PaymentDbProperties.ConnectionStringName)] + public interface IPaymentDbContext : IEfCoreDbContext + { + DbSet PaymentRequests { get; } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentDbContext.cs b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentDbContext.cs new file mode 100644 index 0000000..c2a066e --- /dev/null +++ b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentDbContext.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Payment.PaymentRequests; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace Payment.EntityFrameworkCore +{ + [ConnectionStringName(PaymentDbProperties.ConnectionStringName)] + public class PaymentDbContext : AbpDbContext, IPaymentDbContext + { + public DbSet PaymentRequests { get; set; } + + public PaymentDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ConfigurePayment(); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentDbContextModelCreatingExtensions.cs b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentDbContextModelCreatingExtensions.cs new file mode 100644 index 0000000..ef9b081 --- /dev/null +++ b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentDbContextModelCreatingExtensions.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using Payment.PaymentRequests; +using Volo.Abp; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Payment.EntityFrameworkCore +{ + public static class PaymentDbContextModelCreatingExtensions + { + public static void ConfigurePayment( + this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + builder.Entity(b => + { + b.ToTable(PaymentDbProperties.DbTablePrefix + "PaymentRequests", PaymentDbProperties.DbSchema); + b.ConfigureByConvention(); + b.Property(x => x.CustomerId).HasMaxLength(PaymentRequestConsts.MaxCustomerIdLength); + b.Property(x => x.ProductId).HasMaxLength(PaymentRequestConsts.MaxProductIdLength); + b.Property(x => x.ProductName).IsRequired().HasMaxLength(PaymentRequestConsts.MaxProductNameLength); + + b.HasIndex(x => x.CustomerId); + b.HasIndex(x => x.State); + }); + } + } +} diff --git a/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentEntityFrameworkCoreModule.cs b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentEntityFrameworkCoreModule.cs new file mode 100644 index 0000000..22602d7 --- /dev/null +++ b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/PaymentEntityFrameworkCoreModule.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Payment.EntityFrameworkCore.Repositories; +using Payment.PaymentRequests; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace Payment.EntityFrameworkCore +{ + [DependsOn( + typeof(PaymentDomainModule), + typeof(AbpEntityFrameworkCoreModule) + )] + public class PaymentEntityFrameworkCoreModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddRepository(); + }); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs new file mode 100644 index 0000000..87f704a --- /dev/null +++ b/modules/payment/src/Payment.EntityFrameworkCore/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Linq.Dynamic.Core; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Payment.PaymentRequests; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Payment.EntityFrameworkCore.Repositories +{ + public class PaymentRequestRepository : EfCoreRepository, IPaymentRequestRepository + { + public PaymentRequestRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task> GetListAsync( + int skipCount, + int maxResultCount, + string sorting, + string productName = null, + DateTime? maxCreationTime = null, + DateTime? minCreationTime = null, + PaymentRequestState? state = null, + CancellationToken cancellationToken = default + ) + { + return await (await GetQueryableAsync()) + .WhereIf(!string.IsNullOrWhiteSpace(productName), x => x.ProductName.Contains(productName)) + .WhereIf(maxCreationTime != null, p => p.CreationTime <= maxCreationTime) + .WhereIf(minCreationTime != null, p => p.CreationTime >= minCreationTime) + .WhereIf(state != null, p => p.State == state) + .OrderBy(string.IsNullOrWhiteSpace(sorting) ? nameof(PaymentRequest.ProductName) : sorting) + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task GetCountAsync( + string productName = null, + DateTime? maxCreationTime = null, + DateTime? minCreationTime = null, + PaymentRequestState? state = null, + CancellationToken cancellationToken = default + ) + { + return await (await GetQueryableAsync()) + .WhereIf(!string.IsNullOrWhiteSpace(productName), x => x.ProductName.Contains(productName)) + .WhereIf(maxCreationTime != null, p => p.CreationTime <= maxCreationTime) + .WhereIf(minCreationTime != null, p => p.CreationTime >= minCreationTime) + .WhereIf(state != null, p => p.State == state) + .CountAsync(GetCancellationToken(cancellationToken)); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.EntityFrameworkCore/FodyWeavers.xml b/modules/payment/src/Payment.EntityFrameworkCore/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/src/Payment.EntityFrameworkCore/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.EntityFrameworkCore/FodyWeavers.xsd b/modules/payment/src/Payment.EntityFrameworkCore/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/src/Payment.EntityFrameworkCore/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.EntityFrameworkCore/Payment.EntityFrameworkCore.csproj b/modules/payment/src/Payment.EntityFrameworkCore/Payment.EntityFrameworkCore.csproj new file mode 100644 index 0000000..e038396 --- /dev/null +++ b/modules/payment/src/Payment.EntityFrameworkCore/Payment.EntityFrameworkCore.csproj @@ -0,0 +1,16 @@ + + + + + + + net6.0 + Payment + + + + + + + + diff --git a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/Payment-generate-proxy.json b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/Payment-generate-proxy.json new file mode 100644 index 0000000..b95c9be --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/Payment-generate-proxy.json @@ -0,0 +1,208 @@ +{ + "modules": { + "payment": { + "rootPath": "payment", + "remoteServiceName": "Payment", + "controllers": { + "Payment.PaymentRequests.PaymentRequestController": { + "controllerName": "PaymentRequest", + "controllerGroupName": "PaymentRequest", + "type": "Payment.PaymentRequests.PaymentRequestController", + "interfaces": [ + { + "type": "Payment.PaymentRequests.IPaymentRequestAppService" + } + ], + "actions": { + "CompleteAsyncByToken": { + "uniqueName": "CompleteAsyncByToken", + "name": "CompleteAsync", + "httpMethod": "POST", + "url": "api/payment/requests/complete/{token}", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "token", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "token", + "name": "token", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": [], + "bindingSourceId": "Path", + "descriptorName": "" + } + ], + "returnValue": { + "type": "Payment.PaymentRequests.PaymentRequestDto", + "typeSimple": "Payment.PaymentRequests.PaymentRequestDto" + }, + "allowAnonymous": null, + "implementFrom": "Payment.PaymentRequests.IPaymentRequestAppService" + }, + "CreateAsyncByInput": { + "uniqueName": "CreateAsyncByInput", + "name": "CreateAsync", + "httpMethod": "POST", + "url": "api/payment/requests", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "input", + "typeAsString": "Payment.PaymentRequests.PaymentRequestCreationDto, Payment.Application.Contracts", + "type": "Payment.PaymentRequests.PaymentRequestCreationDto", + "typeSimple": "Payment.PaymentRequests.PaymentRequestCreationDto", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "input", + "name": "input", + "jsonName": null, + "type": "Payment.PaymentRequests.PaymentRequestCreationDto", + "typeSimple": "Payment.PaymentRequests.PaymentRequestCreationDto", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "Body", + "descriptorName": "" + } + ], + "returnValue": { + "type": "Payment.PaymentRequests.PaymentRequestDto", + "typeSimple": "Payment.PaymentRequests.PaymentRequestDto" + }, + "allowAnonymous": null, + "implementFrom": "Payment.PaymentRequests.IPaymentRequestAppService" + }, + "GetAsyncById": { + "uniqueName": "GetAsyncById", + "name": "GetAsync", + "httpMethod": "GET", + "url": "api/payment/requests/{id}", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "id", + "typeAsString": "System.Guid, System.Private.CoreLib", + "type": "System.Guid", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "id", + "name": "id", + "jsonName": null, + "type": "System.Guid", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": [], + "bindingSourceId": "Path", + "descriptorName": "" + } + ], + "returnValue": { + "type": "Payment.PaymentRequests.PaymentRequestDto", + "typeSimple": "Payment.PaymentRequests.PaymentRequestDto" + }, + "allowAnonymous": null, + "implementFrom": "Payment.PaymentRequests.IPaymentRequestAppService" + }, + "StartPaymentAsyncByInput": { + "uniqueName": "StartPaymentAsyncByInput", + "name": "StartPaymentAsync", + "httpMethod": "POST", + "url": "api/payment/requests/start", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "input", + "typeAsString": "Payment.PaymentRequests.StartPaymentDto, Payment.Application.Contracts", + "type": "Payment.PaymentRequests.StartPaymentDto", + "typeSimple": "Payment.PaymentRequests.StartPaymentDto", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "input", + "name": "input", + "jsonName": null, + "type": "Payment.PaymentRequests.StartPaymentDto", + "typeSimple": "Payment.PaymentRequests.StartPaymentDto", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "Body", + "descriptorName": "" + } + ], + "returnValue": { + "type": "Payment.PaymentRequests.StartPaymentResultDto", + "typeSimple": "Payment.PaymentRequests.StartPaymentResultDto" + }, + "allowAnonymous": null, + "implementFrom": "Payment.PaymentRequests.IPaymentRequestAppService" + }, + "HandleWebhookAsyncByPayload": { + "uniqueName": "HandleWebhookAsyncByPayload", + "name": "HandleWebhookAsync", + "httpMethod": "POST", + "url": "api/payment/requests/webhook", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "payload", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "payload", + "name": "payload", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + } + ], + "returnValue": { + "type": "System.Boolean", + "typeSimple": "boolean" + }, + "allowAnonymous": null, + "implementFrom": "Payment.PaymentRequests.IPaymentRequestAppService" + } + } + } + } + } + }, + "types": {} +} \ No newline at end of file diff --git a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs new file mode 100644 index 0000000..ea9f8fa --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.Generated.cs @@ -0,0 +1,58 @@ +// This file is automatically generated by ABP framework to use MVC Controllers from CSharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Http.Client; +using Volo.Abp.Http.Modeling; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using Payment.PaymentRequests; + +// ReSharper disable once CheckNamespace +namespace Payment.PaymentRequests.ClientProxies +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(IPaymentRequestAppService), typeof(PaymentRequestClientProxy))] + public partial class PaymentRequestClientProxy : ClientProxyBase, IPaymentRequestAppService + { + public virtual async Task CompleteAsync(string token) + { + return await RequestAsync(nameof(CompleteAsync), new ClientProxyRequestTypeValue + { + { typeof(string), token } + }); + } + + public virtual async Task CreateAsync(PaymentRequestCreationDto input) + { + return await RequestAsync(nameof(CreateAsync), new ClientProxyRequestTypeValue + { + { typeof(PaymentRequestCreationDto), input } + }); + } + + public virtual async Task GetAsync(Guid id) + { + return await RequestAsync(nameof(GetAsync), new ClientProxyRequestTypeValue + { + { typeof(Guid), id } + }); + } + + public virtual async Task StartPaymentAsync(StartPaymentDto input) + { + return await RequestAsync(nameof(StartPaymentAsync), new ClientProxyRequestTypeValue + { + { typeof(StartPaymentDto), input } + }); + } + + public virtual async Task HandleWebhookAsync(string payload) + { + return await RequestAsync(nameof(HandleWebhookAsync), new ClientProxyRequestTypeValue + { + { typeof(string), payload } + }); + } + } +} diff --git a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.cs b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.cs new file mode 100644 index 0000000..6c18a0a --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/PaymentRequestClientProxy.cs @@ -0,0 +1,8 @@ +// This file is part of PaymentRequestClientProxy, you can customize it here +// ReSharper disable once CheckNamespace +namespace Payment.PaymentRequests.ClientProxies +{ + public partial class PaymentRequestClientProxy + { + } +} diff --git a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/SampleClientProxy.Generated.cs b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/SampleClientProxy.Generated.cs new file mode 100644 index 0000000..970605e --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/SampleClientProxy.Generated.cs @@ -0,0 +1,28 @@ +// This file is automatically generated by ABP framework to use MVC Controllers from CSharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Http.Client; +using Volo.Abp.Http.Modeling; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using Payment.Samples; + +// ReSharper disable once CheckNamespace +namespace Payment.Samples.ClientProxies +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(ISampleAppService), typeof(SampleClientProxy))] + public partial class SampleClientProxy : ClientProxyBase, ISampleAppService + { + public virtual async Task GetAsync() + { + return await RequestAsync(nameof(GetAsync)); + } + + public virtual async Task GetAuthorizedAsync() + { + return await RequestAsync(nameof(GetAuthorizedAsync)); + } + } +} diff --git a/modules/payment/src/Payment.HttpApi.Client/ClientProxies/SampleClientProxy.cs b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/SampleClientProxy.cs new file mode 100644 index 0000000..948c2ea --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/ClientProxies/SampleClientProxy.cs @@ -0,0 +1,8 @@ +// This file is part of SampleClientProxy, you can customize it here +// ReSharper disable once CheckNamespace +namespace Payment.Samples.ClientProxies +{ + public partial class SampleClientProxy + { + } +} diff --git a/modules/payment/src/Payment.HttpApi.Client/FodyWeavers.xml b/modules/payment/src/Payment.HttpApi.Client/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.HttpApi.Client/FodyWeavers.xsd b/modules/payment/src/Payment.HttpApi.Client/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.HttpApi.Client/Payment.HttpApi.Client.csproj b/modules/payment/src/Payment.HttpApi.Client/Payment.HttpApi.Client.csproj new file mode 100644 index 0000000..3b79482 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/Payment.HttpApi.Client.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + Payment + + + + + + + + + + + + + diff --git a/modules/payment/src/Payment.HttpApi.Client/PaymentHttpApiClientModule.cs b/modules/payment/src/Payment.HttpApi.Client/PaymentHttpApiClientModule.cs new file mode 100644 index 0000000..7ad7e88 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi.Client/PaymentHttpApiClientModule.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace Payment +{ + [DependsOn( + typeof(PaymentApplicationContractsModule), + typeof(AbpHttpClientModule))] + public class PaymentHttpApiClientModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddStaticHttpClientProxies( + typeof(PaymentApplicationContractsModule).Assembly, + PaymentRemoteServiceConsts.RemoteServiceName + ); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + } + } +} diff --git a/modules/payment/src/Payment.HttpApi/FodyWeavers.xml b/modules/payment/src/Payment.HttpApi/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.HttpApi/FodyWeavers.xsd b/modules/payment/src/Payment.HttpApi/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.HttpApi/Payment.HttpApi.csproj b/modules/payment/src/Payment.HttpApi/Payment.HttpApi.csproj new file mode 100644 index 0000000..40cc458 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi/Payment.HttpApi.csproj @@ -0,0 +1,16 @@ + + + + + + + net6.0 + Payment + + + + + + + + diff --git a/modules/payment/src/Payment.HttpApi/PaymentController.cs b/modules/payment/src/Payment.HttpApi/PaymentController.cs new file mode 100644 index 0000000..000a3f9 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi/PaymentController.cs @@ -0,0 +1,13 @@ +using Payment.Localization; +using Volo.Abp.AspNetCore.Mvc; + +namespace Payment +{ + public abstract class PaymentController : AbpControllerBase + { + protected PaymentController() + { + LocalizationResource = typeof(PaymentResource); + } + } +} diff --git a/modules/payment/src/Payment.HttpApi/PaymentHttpApiModule.cs b/modules/payment/src/Payment.HttpApi/PaymentHttpApiModule.cs new file mode 100644 index 0000000..10b41af --- /dev/null +++ b/modules/payment/src/Payment.HttpApi/PaymentHttpApiModule.cs @@ -0,0 +1,33 @@ +using Localization.Resources.AbpUi; +using Payment.Localization; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Microsoft.Extensions.DependencyInjection; + +namespace Payment +{ + [DependsOn( + typeof(PaymentApplicationContractsModule), + typeof(AbpAspNetCoreMvcModule))] + public class PaymentHttpApiModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(PaymentHttpApiModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpUiResource)); + }); + } + } +} diff --git a/modules/payment/src/Payment.HttpApi/PaymentRequests/PaymentRequestController.cs b/modules/payment/src/Payment.HttpApi/PaymentRequests/PaymentRequestController.cs new file mode 100644 index 0000000..06e9972 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi/PaymentRequests/PaymentRequestController.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp; + +namespace Payment.PaymentRequests +{ + [RemoteService(Name = PaymentRemoteServiceConsts.RemoteServiceName)] + [Area("payment")] + [Route("api/payment/requests")] + public class PaymentRequestController : PaymentController, IPaymentRequestAppService + { + protected IPaymentRequestAppService PaymentRequestAppService { get; } + + public PaymentRequestController(IPaymentRequestAppService paymentRequestAppService) + { + PaymentRequestAppService = paymentRequestAppService; + } + + [HttpPost] + [Route("complete/{token}")] + public Task CompleteAsync([FromRoute] string token) + { + return PaymentRequestAppService.CompleteAsync(token); + } + + [HttpPost] + public Task CreateAsync(PaymentRequestCreationDto input) + { + return PaymentRequestAppService.CreateAsync(input); + } + + [HttpGet] + [Route("{id}")] + public Task GetAsync(Guid id) + { + return PaymentRequestAppService.GetAsync(id); + } + + [HttpPost] + [Route("start")] + public Task StartPaymentAsync(StartPaymentDto input) + { + return PaymentRequestAppService.StartPaymentAsync(input); + } + + [ApiExplorerSettings(IgnoreApi = true)] + [HttpPost] + [Route("webhook")] + public async Task HandleWebhookAsync(string payload) + { + var bytes = await Request.Body.GetAllBytesAsync(); + payload = Encoding.UTF8.GetString(bytes); + + return await PaymentRequestAppService.HandleWebhookAsync(payload); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.HttpApi/Samples/SampleController.cs b/modules/payment/src/Payment.HttpApi/Samples/SampleController.cs new file mode 100644 index 0000000..4bf9f61 --- /dev/null +++ b/modules/payment/src/Payment.HttpApi/Samples/SampleController.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; + +namespace Payment.Samples +{ + [Area("Payment")] + [RemoteService(Name = PaymentRemoteServiceConsts.RemoteServiceName)] + [Route("api/Payment/sample")] + public class SampleController : PaymentController, ISampleAppService + { + private readonly ISampleAppService _sampleAppService; + + public SampleController(ISampleAppService sampleAppService) + { + _sampleAppService = sampleAppService; + } + + [HttpGet] + public async Task GetAsync() + { + return await _sampleAppService.GetAsync(); + } + + [HttpGet] + [Route("authorized")] + [Authorize] + public async Task GetAuthorizedAsync() + { + return await _sampleAppService.GetAsync(); + } + } +} diff --git a/modules/payment/src/Payment.Web/FodyWeavers.xml b/modules/payment/src/Payment.Web/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/src/Payment.Web/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/FodyWeavers.xsd b/modules/payment/src/Payment.Web/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/src/Payment.Web/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Menus/PaymentMenuContributor.cs b/modules/payment/src/Payment.Web/Menus/PaymentMenuContributor.cs new file mode 100644 index 0000000..095aa5c --- /dev/null +++ b/modules/payment/src/Payment.Web/Menus/PaymentMenuContributor.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Volo.Abp.UI.Navigation; + +namespace Payment.Web.Menus +{ + public class PaymentMenuContributor : IMenuContributor + { + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + await ConfigureMainMenuAsync(context); + } + } + + private Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + //Add main menu items. + context.Menu.AddItem(new ApplicationMenuItem(PaymentMenus.Prefix, displayName: "Payment", "~/Payment", icon: "fa fa-globe")); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Menus/PaymentMenus.cs b/modules/payment/src/Payment.Web/Menus/PaymentMenus.cs new file mode 100644 index 0000000..577d6cc --- /dev/null +++ b/modules/payment/src/Payment.Web/Menus/PaymentMenus.cs @@ -0,0 +1,11 @@ +namespace Payment.Web.Menus +{ + public class PaymentMenus + { + public const string Prefix = "Payment"; + + //Add your menu items here... + //public const string Home = Prefix + ".MyNewMenuItem"; + + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Pages/Payment/PostCheckout.cshtml b/modules/payment/src/Payment.Web/Pages/Payment/PostCheckout.cshtml new file mode 100644 index 0000000..4114ab8 --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/Payment/PostCheckout.cshtml @@ -0,0 +1,41 @@ +@page +@using Payment.Web.Pages.Payment +@using Payment.PaymentRequests +@inherits Payment.Web.Pages.PaymentPageBase +@model Payment.Web.Pages.Payment.PostCheckoutPageModel +@{ + PageLayout.Content.Title = L["PaymentResult"] + L["Enum:PaymentRequestState:" + Model.PaymentRequest.State]; +} + + + + + @L["PaymentResult"] + + + + + @if (Model.PaymentRequest.State == PaymentRequestState.Completed) + { + + + + @L["YourPaymentCompletedSuccessfully"] + > + + } + else + { + + + + @L["YourPaymentFailed"] + + + + + } + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Pages/Payment/PostCheckout.cshtml.cs b/modules/payment/src/Payment.Web/Pages/Payment/PostCheckout.cshtml.cs new file mode 100644 index 0000000..26e5c54 --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/Payment/PostCheckout.cshtml.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Payment.PaymentRequests; +using Payment.Web.PaymentRequest; +using System; +using System.Threading.Tasks; + +namespace Payment.Web.Pages.Payment +{ + public class PostCheckoutPageModel : PaymentPageModel + { + [BindProperty(SupportsGet = true)] + public string Token { get; set; } + + public PaymentRequestDto PaymentRequest { get; protected set; } + + public string GoBackLink { get; set; } + + private readonly IPaymentRequestAppService _paymentRequestAppService; + private readonly PaymentWebOptions _paymentWebOptions; + private readonly IPaymentUrlBuilder _paymentUrlBuilder; + + public PostCheckoutPageModel( + IPaymentRequestAppService appService, + IOptions paymentWebOptions, + IPaymentUrlBuilder paymentUrlBuilder) + { + _paymentRequestAppService = appService; + _paymentWebOptions = paymentWebOptions.Value; + _paymentUrlBuilder = paymentUrlBuilder; + } + + public async Task OnGetAsync() + { + if (Token.IsNullOrWhiteSpace()) + { + return BadRequest(); + } + + PaymentRequest = await _paymentRequestAppService.CompleteAsync(Token); + + GoBackLink = _paymentUrlBuilder.BuildCheckoutUrl(PaymentRequest.Id).AbsoluteUri; + + if (PaymentRequest.State == PaymentRequestState.Completed + && !_paymentWebOptions.PaymentSuccessfulCallbackUrl.IsNullOrWhiteSpace()) + { + var callbackUrl = _paymentWebOptions.PaymentSuccessfulCallbackUrl + "?paymentRequestId=" + PaymentRequest.Id; + Response.Redirect(callbackUrl); + } + + if (PaymentRequest.State == PaymentRequestState.Failed + && !_paymentWebOptions.PaymentFailureCallbackUrl.IsNullOrWhiteSpace()) + { + var callbackUrl = _paymentWebOptions.PaymentFailureCallbackUrl + "?paymentRequestId=" + PaymentRequest.Id; + Response.Redirect(callbackUrl); + } + + return Page(); + } + } +} diff --git a/modules/payment/src/Payment.Web/Pages/Payment/PreCheckout.cshtml b/modules/payment/src/Payment.Web/Pages/Payment/PreCheckout.cshtml new file mode 100644 index 0000000..a462101 --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/Payment/PreCheckout.cshtml @@ -0,0 +1,38 @@ +@page +@using Payment.Web.Pages.Payment +@using Payment.PaymentRequests +@inherits Payment.Web.Pages.PaymentPageBase +@model Payment.Web.Pages.Payment.PreCheckoutPageModel + +@section scripts { + + + +} + + + + + @L["ProductInformation"] + + + + @L["ProductName"]: @Model.PaymentRequest.ProductName + @L["Price"]: @Model.PaymentRequest.Price @Model.PaymentRequest.Currency + @L["State"]: @L["Enum:PaymentRequestState:" + Model.PaymentRequest.State] + + + + + @if (Model.PaymentRequest.State != PaymentRequestState.Waiting) + { + @L["PaymentRequestIsNotWaiting"] + } + else + { + @L["CompletePayment"] + } + + + + \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Pages/Payment/PreCheckout.cshtml.cs b/modules/payment/src/Payment.Web/Pages/Payment/PreCheckout.cshtml.cs new file mode 100644 index 0000000..0902d77 --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/Payment/PreCheckout.cshtml.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Mvc; +using Payment.PaymentRequests; +using Payment.Web.PaymentRequest; +using System; +using System.Threading.Tasks; +using Volo.Abp; + +namespace Payment.Web.Pages.Payment +{ + public class PreCheckoutPageModel : PaymentPageModel + { + [BindProperty(SupportsGet = true)] + public Guid PaymentRequestId { get; set; } + + public PaymentRequestDto PaymentRequest { get; set; } + + private readonly IPaymentRequestAppService _paymentRequestAppService; + private readonly IPaymentUrlBuilder _paymentUrlBuilder; + + public PreCheckoutPageModel( + IPaymentRequestAppService paymentRequestAppService, + IPaymentUrlBuilder paymentUrlBuilder) + { + _paymentRequestAppService = paymentRequestAppService; + _paymentUrlBuilder = paymentUrlBuilder; + } + + public async Task OnGetAsync() + { + PaymentRequest = await _paymentRequestAppService.GetAsync(PaymentRequestId); + + return Page(); + } + + public async Task OnPostAsync() + { + PaymentRequest = await _paymentRequestAppService.GetAsync(PaymentRequestId); + + var result = await _paymentRequestAppService.StartPaymentAsync(new StartPaymentDto + { + PaymentRequestId = PaymentRequest.Id, + ReturnUrl = _paymentUrlBuilder.BuildReturnUrl(PaymentRequestId).AbsoluteUri, + CancelUrl = _paymentUrlBuilder.BuildCheckoutUrl(PaymentRequestId).AbsoluteUri + }); + + Response.Redirect(result.CheckoutLink); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs b/modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs new file mode 100644 index 0000000..8e2dc9e --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/PaymentPageBase.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.Extensions.Localization; +using Payment.Localization; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.UI.Layout; + +namespace Payment.Web.Pages +{ + public class PaymentPageBase : Microsoft.AspNetCore.Mvc.RazorPages.Page + { + [RazorInject] public IStringLocalizer L { get; set; } + + [RazorInject] public IPageLayout PageLayout { get; set; } + + public override Task ExecuteAsync() + { + return Task.CompletedTask; // Will be overriden by razor pages. (.cshtml) + } + } +} diff --git a/modules/payment/src/Payment.Web/Pages/PaymentPageModel.cs b/modules/payment/src/Payment.Web/Pages/PaymentPageModel.cs new file mode 100644 index 0000000..ae1f6c7 --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/PaymentPageModel.cs @@ -0,0 +1,16 @@ +using Payment.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Payment.Web.Pages +{ + /* Inherit your PageModel classes from this class. + */ + public abstract class PaymentPageModel : AbpPageModel + { + protected PaymentPageModel() + { + LocalizationResourceType = typeof(PaymentResource); + ObjectMapperContext = typeof(PaymentWebModule); + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Pages/_ViewImports.cshtml b/modules/payment/src/Payment.Web/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..c1da1f5 --- /dev/null +++ b/modules/payment/src/Payment.Web/Pages/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/Payment.Web.csproj b/modules/payment/src/Payment.Web/Payment.Web.csproj new file mode 100644 index 0000000..60366a6 --- /dev/null +++ b/modules/payment/src/Payment.Web/Payment.Web.csproj @@ -0,0 +1,41 @@ + + + + + + + net6.0 + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + true + Library + Payment.Web + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs b/modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs new file mode 100644 index 0000000..31e6649 --- /dev/null +++ b/modules/payment/src/Payment.Web/PaymentRequest/IPaymentUrlBuilder.cs @@ -0,0 +1,11 @@ +using System; + +namespace Payment.Web.PaymentRequest +{ + public interface IPaymentUrlBuilder + { + Uri BuildCheckoutUrl(Guid paymentRequestId); + + Uri BuildReturnUrl(Guid paymentRequestId); + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs b/modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs new file mode 100644 index 0000000..03e797f --- /dev/null +++ b/modules/payment/src/Payment.Web/PaymentRequest/PaymentUrlBuilder.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Options; +using System; +using Volo.Abp.DependencyInjection; +using Volo.Abp.UI.Navigation.Urls; + +namespace Payment.Web.PaymentRequest +{ + public class PaymentUrlBuilder : ITransientDependency, IPaymentUrlBuilder + { + private readonly AppUrlOptions urlOptions; + + public PaymentUrlBuilder(IOptions urlOptions) + { + this.urlOptions = urlOptions.Value; + } + + public virtual Uri BuildCheckoutUrl(Guid paymentRequestId) + { + return new Uri(urlOptions.Applications["MVC"].RootUrl.EnsureEndsWith('/') + "Payment/PreCheckout?paymentRequestId=" + paymentRequestId); + } + + public Uri BuildReturnUrl(Guid paymentRequestId) + { + return new Uri(urlOptions.Applications["MVC"].RootUrl.EnsureEndsWith('/') + "Payment/PostCheckout?paymentRequestId=" + paymentRequestId); + } + } +} diff --git a/modules/payment/src/Payment.Web/PaymentWebAutoMapperProfile.cs b/modules/payment/src/Payment.Web/PaymentWebAutoMapperProfile.cs new file mode 100644 index 0000000..364e293 --- /dev/null +++ b/modules/payment/src/Payment.Web/PaymentWebAutoMapperProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; + +namespace Payment.Web +{ + public class PaymentWebAutoMapperProfile : Profile + { + public PaymentWebAutoMapperProfile() + { + /* You can configure your AutoMapper mapping configuration here. + * Alternatively, you can split your mapping configurations + * into multiple profile classes for a better organization. */ + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/PaymentWebModule.cs b/modules/payment/src/Payment.Web/PaymentWebModule.cs new file mode 100644 index 0000000..48cf24d --- /dev/null +++ b/modules/payment/src/Payment.Web/PaymentWebModule.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.DependencyInjection; +using Payment.Localization; +using Payment.Web.Menus; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; +using Volo.Abp.UI.Navigation; +using Volo.Abp.VirtualFileSystem; + +namespace Payment.Web +{ + [DependsOn( + typeof(PaymentApplicationContractsModule), + typeof(AbpAspNetCoreMvcUiThemeSharedModule), + typeof(AbpAutoMapperModule), + typeof(AbpAspNetCoreMvcModule) + )] + public class PaymentWebModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.PreConfigure(options => + { + options.AddAssemblyResource(typeof(PaymentResource), typeof(PaymentWebModule).Assembly); + }); + + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(PaymentWebModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.MenuContributors.Add(new PaymentMenuContributor()); + }); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + context.Services.AddAutoMapperObjectMapper(); + Configure(options => + { + options.AddMaps(validate: true); + }); + + Configure(options => + { + //Configure authorization. + }); + } + } +} diff --git a/modules/payment/src/Payment.Web/PaymentWebOptions.cs b/modules/payment/src/Payment.Web/PaymentWebOptions.cs new file mode 100644 index 0000000..4e6017e --- /dev/null +++ b/modules/payment/src/Payment.Web/PaymentWebOptions.cs @@ -0,0 +1,8 @@ +namespace Payment.Web +{ + public class PaymentWebOptions + { + public string PaymentSuccessfulCallbackUrl { get; set; } + public string PaymentFailureCallbackUrl { get; set; } + } +} diff --git a/modules/payment/src/Payment.Web/Properties/launchSettings.json b/modules/payment/src/Payment.Web/Properties/launchSettings.json new file mode 100644 index 0000000..a8c7564 --- /dev/null +++ b/modules/payment/src/Payment.Web/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:64779/", + "sslPort": 44326 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Payment.Web": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/modules/payment/src/Payment.Web/wwwroot/client-proxies/Payment-proxy.js b/modules/payment/src/Payment.Web/wwwroot/client-proxies/Payment-proxy.js new file mode 100644 index 0000000..bd8b2ec --- /dev/null +++ b/modules/payment/src/Payment.Web/wwwroot/client-proxies/Payment-proxy.js @@ -0,0 +1,32 @@ +/* This file is automatically generated by ABP framework to use MVC Controllers from javascript. */ + + +// module payment + +(function(){ + + // controller payment.samples.sample + + (function(){ + + abp.utils.createNamespace(window, 'payment.samples.sample'); + + payment.samples.sample.get = function(ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/Payment/sample', + type: 'GET' + }, ajaxParams)); + }; + + payment.samples.sample.getAuthorized = function(ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/Payment/sample/authorized', + type: 'GET' + }, ajaxParams)); + }; + + })(); + +})(); + + diff --git a/modules/payment/test/Payment.Admin.Application.Tests/FodyWeavers.xml b/modules/payment/test/Payment.Admin.Application.Tests/FodyWeavers.xml new file mode 100644 index 0000000..be0de3a --- /dev/null +++ b/modules/payment/test/Payment.Admin.Application.Tests/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.Admin.Application.Tests/FodyWeavers.xsd b/modules/payment/test/Payment.Admin.Application.Tests/FodyWeavers.xsd new file mode 100644 index 0000000..3f3946e --- /dev/null +++ b/modules/payment/test/Payment.Admin.Application.Tests/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.Admin.Application.Tests/Payment.Admin.Application.Tests.csproj b/modules/payment/test/Payment.Admin.Application.Tests/Payment.Admin.Application.Tests.csproj new file mode 100644 index 0000000..75350f8 --- /dev/null +++ b/modules/payment/test/Payment.Admin.Application.Tests/Payment.Admin.Application.Tests.csproj @@ -0,0 +1,15 @@ + + + + + + net6.0 + + + + + + + + + diff --git a/modules/payment/test/Payment.Admin.Application.Tests/PaymentAdminApplicationTestBase.cs b/modules/payment/test/Payment.Admin.Application.Tests/PaymentAdminApplicationTestBase.cs new file mode 100644 index 0000000..9e363d5 --- /dev/null +++ b/modules/payment/test/Payment.Admin.Application.Tests/PaymentAdminApplicationTestBase.cs @@ -0,0 +1,7 @@ +namespace Payment.Admin.Application.Tests +{ + public abstract class PaymentAdminApplicationTestBase : PaymentTestBase + { + + } +} \ No newline at end of file diff --git a/modules/payment/test/Payment.Admin.Application.Tests/PaymentAdminApplicationTestModule.cs b/modules/payment/test/Payment.Admin.Application.Tests/PaymentAdminApplicationTestModule.cs new file mode 100644 index 0000000..8c72d36 --- /dev/null +++ b/modules/payment/test/Payment.Admin.Application.Tests/PaymentAdminApplicationTestModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Modularity; + +namespace Payment.Admin.Application.Tests +{ + [DependsOn( + typeof(PaymentDomainTestModule), + typeof(PaymentAdminApplicationModule) + )] + public class PaymentAdminApplicationTestModule : AbpModule + { + + } +} \ No newline at end of file diff --git a/modules/payment/test/Payment.Admin.Application.Tests/PaymentRequests/PaymentRequestAdminAppService_Tests.cs b/modules/payment/test/Payment.Admin.Application.Tests/PaymentRequests/PaymentRequestAdminAppService_Tests.cs new file mode 100644 index 0000000..ff88bae --- /dev/null +++ b/modules/payment/test/Payment.Admin.Application.Tests/PaymentRequests/PaymentRequestAdminAppService_Tests.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Payment.Admin.Payments; +using Shouldly; +using Xunit; + +namespace Payment.Admin.Application.Tests.PaymentRequests +{ + public class PaymentRequestAdminAppService_Tests : PaymentAdminApplicationTestBase + { + private readonly IPaymentRequestAdminAppService _paymentRequestAdminAppService; + + public PaymentRequestAdminAppService_Tests() + { + _paymentRequestAdminAppService = GetRequiredService(); + } + + [Fact] + public async Task GetListAsync() + { + var paymentRequests = await _paymentRequestAdminAppService.GetListAsync(new PaymentRequestGetListInput()); + + paymentRequests.Items.ShouldNotBeNull(); + paymentRequests.Items.Count.ShouldBeGreaterThanOrEqualTo(1); + paymentRequests.TotalCount.ShouldBeGreaterThanOrEqualTo(1); + } + } +} \ No newline at end of file diff --git a/modules/payment/test/Payment.Application.Tests/FodyWeavers.xml b/modules/payment/test/Payment.Application.Tests/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/test/Payment.Application.Tests/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.Application.Tests/FodyWeavers.xsd b/modules/payment/test/Payment.Application.Tests/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/test/Payment.Application.Tests/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.Application.Tests/Payment.Application.Tests.csproj b/modules/payment/test/Payment.Application.Tests/Payment.Application.Tests.csproj new file mode 100644 index 0000000..255e668 --- /dev/null +++ b/modules/payment/test/Payment.Application.Tests/Payment.Application.Tests.csproj @@ -0,0 +1,16 @@ + + + + + + net6.0 + Payment + + + + + + + + + diff --git a/modules/payment/test/Payment.Application.Tests/PaymentApplicationTestBase.cs b/modules/payment/test/Payment.Application.Tests/PaymentApplicationTestBase.cs new file mode 100644 index 0000000..27205bf --- /dev/null +++ b/modules/payment/test/Payment.Application.Tests/PaymentApplicationTestBase.cs @@ -0,0 +1,10 @@ +namespace Payment +{ + /* Inherit from this class for your application layer tests. + * See SampleAppService_Tests for example. + */ + public abstract class PaymentApplicationTestBase : PaymentTestBase + { + + } +} \ No newline at end of file diff --git a/modules/payment/test/Payment.Application.Tests/PaymentApplicationTestModule.cs b/modules/payment/test/Payment.Application.Tests/PaymentApplicationTestModule.cs new file mode 100644 index 0000000..a9dc888 --- /dev/null +++ b/modules/payment/test/Payment.Application.Tests/PaymentApplicationTestModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Modularity; + +namespace Payment +{ + [DependsOn( + typeof(PaymentApplicationModule), + typeof(PaymentDomainTestModule) + )] + public class PaymentApplicationTestModule : AbpModule + { + + } +} diff --git a/modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs b/modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs new file mode 100644 index 0000000..b5c75b6 --- /dev/null +++ b/modules/payment/test/Payment.Application.Tests/PaymentRequests/PaymentRequestAppService_Tests.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace Payment.PaymentRequests +{ + public class PaymentRequestAppService_Tests : PaymentApplicationTestBase + { + private readonly IPaymentRequestAppService _paymentRequestAppService; + + public PaymentRequestAppService_Tests() + { + _paymentRequestAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Create_Minimal_Payment_Request() + { + var result = await _paymentRequestAppService.CreateAsync( + new PaymentRequestCreationDto + { + CustomerId = "customer_1", + ProductId = "my_product_1", + ProductName = "My product 1", + Price = 99.99m + } + ); + + result.Id.ShouldNotBe(Guid.Empty); + result.Price.ShouldBe(99.99m); + result.ProductName.ShouldBe("My product 1"); + } + + [Fact] + public async Task Should_Create_Payment_Request_With_Default_Currency() + { + var result = await _paymentRequestAppService.CreateAsync(new PaymentRequestCreationDto + { + CustomerId = "customer_1", + ProductId = "my_product_1", + Price = 4.95m, + ProductName = "Donation" + }); + + var options = GetRequiredService>(); + + result.Currency.ShouldBe(options.Value.DefaultCurrency); + } + } +} diff --git a/modules/payment/test/Payment.Application.Tests/Samples/SampleAppService_Tests.cs b/modules/payment/test/Payment.Application.Tests/Samples/SampleAppService_Tests.cs new file mode 100644 index 0000000..a989392 --- /dev/null +++ b/modules/payment/test/Payment.Application.Tests/Samples/SampleAppService_Tests.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Payment.Samples +{ + public class SampleAppService_Tests : PaymentApplicationTestBase + { + private readonly ISampleAppService _sampleAppService; + + public SampleAppService_Tests() + { + _sampleAppService = GetRequiredService(); + } + + [Fact] + public async Task GetAsync() + { + var result = await _sampleAppService.GetAsync(); + result.Value.ShouldBe(42); + } + + [Fact] + public async Task GetAuthorizedAsync() + { + var result = await _sampleAppService.GetAuthorizedAsync(); + result.Value.ShouldBe(42); + } + } +} diff --git a/modules/payment/test/Payment.Domain.Tests/FodyWeavers.xml b/modules/payment/test/Payment.Domain.Tests/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/test/Payment.Domain.Tests/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.Domain.Tests/FodyWeavers.xsd b/modules/payment/test/Payment.Domain.Tests/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/test/Payment.Domain.Tests/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.Domain.Tests/Payment.Domain.Tests.csproj b/modules/payment/test/Payment.Domain.Tests/Payment.Domain.Tests.csproj new file mode 100644 index 0000000..2265273 --- /dev/null +++ b/modules/payment/test/Payment.Domain.Tests/Payment.Domain.Tests.csproj @@ -0,0 +1,15 @@ + + + + + + net6.0 + Payment + + + + + + + + diff --git a/modules/payment/test/Payment.Domain.Tests/PaymentDomainTestBase.cs b/modules/payment/test/Payment.Domain.Tests/PaymentDomainTestBase.cs new file mode 100644 index 0000000..f5ff537 --- /dev/null +++ b/modules/payment/test/Payment.Domain.Tests/PaymentDomainTestBase.cs @@ -0,0 +1,10 @@ +namespace Payment +{ + /* Inherit from this class for your domain layer tests. + * See SampleManager_Tests for example. + */ + public abstract class PaymentDomainTestBase : PaymentTestBase + { + + } +} \ No newline at end of file diff --git a/modules/payment/test/Payment.Domain.Tests/PaymentDomainTestModule.cs b/modules/payment/test/Payment.Domain.Tests/PaymentDomainTestModule.cs new file mode 100644 index 0000000..e6809a8 --- /dev/null +++ b/modules/payment/test/Payment.Domain.Tests/PaymentDomainTestModule.cs @@ -0,0 +1,17 @@ +using Payment.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace Payment +{ + /* Domain tests are configured to use the EF Core provider. + * You can switch to MongoDB, however your domain tests should be + * database independent anyway. + */ + [DependsOn( + typeof(PaymentEntityFrameworkCoreTestModule) + )] + public class PaymentDomainTestModule : AbpModule + { + + } +} diff --git a/modules/payment/test/Payment.Domain.Tests/Samples/SampleManager_Tests.cs b/modules/payment/test/Payment.Domain.Tests/Samples/SampleManager_Tests.cs new file mode 100644 index 0000000..476c936 --- /dev/null +++ b/modules/payment/test/Payment.Domain.Tests/Samples/SampleManager_Tests.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Xunit; + +namespace Payment.Samples +{ + public class SampleManager_Tests : PaymentDomainTestBase + { + //private readonly SampleManager _sampleManager; + + public SampleManager_Tests() + { + //_sampleManager = GetRequiredService(); + } + + [Fact] + public async Task Method1Async() + { + + } + } +} diff --git a/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/PaymentEntityFrameworkCoreTestBase.cs b/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/PaymentEntityFrameworkCoreTestBase.cs new file mode 100644 index 0000000..51ccf90 --- /dev/null +++ b/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/PaymentEntityFrameworkCoreTestBase.cs @@ -0,0 +1,10 @@ +namespace Payment.EntityFrameworkCore +{ + /* This class can be used as a base class for EF Core integration tests, + * while SampleRepository_Tests uses a different approach. + */ + public abstract class PaymentEntityFrameworkCoreTestBase : PaymentTestBase + { + + } +} \ No newline at end of file diff --git a/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/PaymentEntityFrameworkCoreTestModule.cs b/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/PaymentEntityFrameworkCoreTestModule.cs new file mode 100644 index 0000000..a8aa254 --- /dev/null +++ b/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/PaymentEntityFrameworkCoreTestModule.cs @@ -0,0 +1,43 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.Sqlite; +using Volo.Abp.Modularity; + +namespace Payment.EntityFrameworkCore +{ + [DependsOn( + typeof(PaymentTestBaseModule), + typeof(PaymentEntityFrameworkCoreModule), + typeof(AbpEntityFrameworkCoreSqliteModule) + )] + public class PaymentEntityFrameworkCoreTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var sqliteConnection = CreateDatabaseAndGetConnection(); + + Configure(options => + { + options.Configure(abpDbContextConfigurationContext => + { + abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection); + }); + }); + } + + private static SqliteConnection CreateDatabaseAndGetConnection() + { + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + + new PaymentDbContext( + new DbContextOptionsBuilder().UseSqlite(connection).Options + ).GetService().CreateTables(); + + return connection; + } + } +} diff --git a/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs b/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs new file mode 100644 index 0000000..5dcd5b2 --- /dev/null +++ b/modules/payment/test/Payment.EntityFrameworkCore.Tests/EntityFrameworkCore/Samples/SampleRepository_Tests.cs @@ -0,0 +1,12 @@ +using Payment.Samples; + +namespace Payment.EntityFrameworkCore.Samples +{ + public class SampleRepository_Tests : SampleRepository_Tests + { + /* Don't write custom repository tests here, instead write to + * the base class. + * One exception can be some specific tests related to EF core. + */ + } +} diff --git a/modules/payment/test/Payment.EntityFrameworkCore.Tests/FodyWeavers.xml b/modules/payment/test/Payment.EntityFrameworkCore.Tests/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/test/Payment.EntityFrameworkCore.Tests/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.EntityFrameworkCore.Tests/FodyWeavers.xsd b/modules/payment/test/Payment.EntityFrameworkCore.Tests/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/test/Payment.EntityFrameworkCore.Tests/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.EntityFrameworkCore.Tests/Payment.EntityFrameworkCore.Tests.csproj b/modules/payment/test/Payment.EntityFrameworkCore.Tests/Payment.EntityFrameworkCore.Tests.csproj new file mode 100644 index 0000000..73e3178 --- /dev/null +++ b/modules/payment/test/Payment.EntityFrameworkCore.Tests/Payment.EntityFrameworkCore.Tests.csproj @@ -0,0 +1,18 @@ + + + + + + net6.0 + Payment + + + + + + + + + + + diff --git a/modules/payment/test/Payment.TestBase/FodyWeavers.xml b/modules/payment/test/Payment.TestBase/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/payment/test/Payment.TestBase/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.TestBase/FodyWeavers.xsd b/modules/payment/test/Payment.TestBase/FodyWeavers.xsd new file mode 100644 index 0000000..ffa6fc4 --- /dev/null +++ b/modules/payment/test/Payment.TestBase/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/payment/test/Payment.TestBase/Payment.TestBase.csproj b/modules/payment/test/Payment.TestBase/Payment.TestBase.csproj new file mode 100644 index 0000000..4d3acbb --- /dev/null +++ b/modules/payment/test/Payment.TestBase/Payment.TestBase.csproj @@ -0,0 +1,23 @@ + + + + + + net6.0 + Payment + + + + + + + + + + + + + + + + diff --git a/modules/payment/test/Payment.TestBase/PaymentDataSeedContributor.cs b/modules/payment/test/Payment.TestBase/PaymentDataSeedContributor.cs new file mode 100644 index 0000000..9da50d2 --- /dev/null +++ b/modules/payment/test/Payment.TestBase/PaymentDataSeedContributor.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using Payment.PaymentRequests; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Payment +{ + public class PaymentDataSeedContributor : IDataSeedContributor, ITransientDependency + { + private readonly IPaymentRequestRepository _paymentRequestRepository; + private readonly PaymentTestData _paymentTestData; + + public PaymentDataSeedContributor(IPaymentRequestRepository paymentRequestRepository, PaymentTestData paymentTestData) + { + _paymentRequestRepository = paymentRequestRepository; + _paymentTestData = paymentTestData; + } + + public async Task SeedAsync(DataSeedContext context) + { + await BuildPaymentRequestAsync(); + } + + private async Task BuildPaymentRequestAsync() + { + var paymentRequest = new PaymentRequest( + _paymentTestData.PaymentRequest1Id, + _paymentTestData.Customer1Id, + _paymentTestData.Product1Id, + "Product 1", + 10, + "USD"); + + await _paymentRequestRepository.InsertAsync(paymentRequest, autoSave: true); + } + } +} diff --git a/modules/payment/test/Payment.TestBase/PaymentTestBase.cs b/modules/payment/test/Payment.TestBase/PaymentTestBase.cs new file mode 100644 index 0000000..5b3dfed --- /dev/null +++ b/modules/payment/test/Payment.TestBase/PaymentTestBase.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; +using Volo.Abp.Uow; +using Volo.Abp.Testing; + +namespace Payment +{ + /* All test classes are derived from this class, directly or indirectly. */ + public abstract class PaymentTestBase : AbpIntegratedTest + where TStartupModule : IAbpModule + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + protected virtual Task WithUnitOfWorkAsync(Func func) + { + return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); + } + + protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action) + { + using (var scope = ServiceProvider.CreateScope()) + { + var uowManager = scope.ServiceProvider.GetRequiredService(); + + using (var uow = uowManager.Begin(options)) + { + await action(); + + await uow.CompleteAsync(); + } + } + } + + protected virtual Task WithUnitOfWorkAsync(Func> func) + { + return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); + } + + protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func> func) + { + using (var scope = ServiceProvider.CreateScope()) + { + var uowManager = scope.ServiceProvider.GetRequiredService(); + + using (var uow = uowManager.Begin(options)) + { + var result = await func(); + await uow.CompleteAsync(); + return result; + } + } + } + } +} diff --git a/modules/payment/test/Payment.TestBase/PaymentTestBaseModule.cs b/modules/payment/test/Payment.TestBase/PaymentTestBaseModule.cs new file mode 100644 index 0000000..a993406 --- /dev/null +++ b/modules/payment/test/Payment.TestBase/PaymentTestBaseModule.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Authorization; +using Volo.Abp.Autofac; +using Volo.Abp.Data; +using Volo.Abp.Modularity; +using Volo.Abp.Threading; + +namespace Payment +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpTestBaseModule), + typeof(AbpAuthorizationModule), + typeof(PaymentDomainModule) + )] + public class PaymentTestBaseModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAlwaysAllowAuthorization(); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + SeedTestData(context); + } + + private static void SeedTestData(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(async () => + { + using (var scope = context.ServiceProvider.CreateScope()) + { + await scope.ServiceProvider + .GetRequiredService() + .SeedAsync(); + } + }); + } + } +} diff --git a/modules/payment/test/Payment.TestBase/PaymentTestData.cs b/modules/payment/test/Payment.TestBase/PaymentTestData.cs new file mode 100644 index 0000000..d3518b7 --- /dev/null +++ b/modules/payment/test/Payment.TestBase/PaymentTestData.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.DependencyInjection; + +namespace Payment +{ + public class PaymentTestData : ISingletonDependency + { + public Guid PaymentRequest1Id { get; } = Guid.NewGuid(); + + public string Customer1Id { get; } = Guid.NewGuid().ToString("N"); + + public string Product1Id { get; } = Guid.NewGuid().ToString("N"); + } +} \ No newline at end of file diff --git a/modules/payment/test/Payment.TestBase/Samples/SampleRepository_Tests.cs b/modules/payment/test/Payment.TestBase/Samples/SampleRepository_Tests.cs new file mode 100644 index 0000000..ef62f66 --- /dev/null +++ b/modules/payment/test/Payment.TestBase/Samples/SampleRepository_Tests.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Volo.Abp.Modularity; +using Xunit; + +namespace Payment.Samples +{ + /* Write your custom repository tests like that, in this project, as abstract classes. + * Then inherit these abstract classes from EF Core & MongoDB test projects. + * In this way, both database providers are tests with the same set tests. + */ + public abstract class SampleRepository_Tests : PaymentTestBase + where TStartupModule : IAbpModule + { + //private readonly ISampleRepository _sampleRepository; + + protected SampleRepository_Tests() + { + //_sampleRepository = GetRequiredService(); + } + + [Fact] + public async Task Method1Async() + { + + } + } +} diff --git a/modules/payment/test/Payment.TestBase/Security/FakeCurrentPrincipalAccessor.cs b/modules/payment/test/Payment.TestBase/Security/FakeCurrentPrincipalAccessor.cs new file mode 100644 index 0000000..c312464 --- /dev/null +++ b/modules/payment/test/Payment.TestBase/Security/FakeCurrentPrincipalAccessor.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Security.Claims; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; + +namespace Payment.Security +{ + [Dependency(ReplaceServices = true)] + public class FakeCurrentPrincipalAccessor : ThreadCurrentPrincipalAccessor + { + protected override ClaimsPrincipal GetClaimsPrincipal() + { + return GetPrincipal(); + } + + private ClaimsPrincipal _principal; + + private ClaimsPrincipal GetPrincipal() + { + if (_principal == null) + { + lock (this) + { + if (_principal == null) + { + _principal = new ClaimsPrincipal( + new ClaimsIdentity( + new List + { + new Claim(AbpClaimTypes.UserId,"2e701e62-0953-4dd3-910b-dc6cc93ccb0d"), + new Claim(AbpClaimTypes.UserName,"admin"), + new Claim(AbpClaimTypes.Email,"admin@abp.io") + } + ) + ); + } + } + } + + return _principal; + } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/EventHub.Admin.Application.Contracts.csproj b/src/EventHub.Admin.Application.Contracts/EventHub.Admin.Application.Contracts.csproj index 9197540..c48c9e7 100644 --- a/src/EventHub.Admin.Application.Contracts/EventHub.Admin.Application.Contracts.csproj +++ b/src/EventHub.Admin.Application.Contracts/EventHub.Admin.Application.Contracts.csproj @@ -9,12 +9,13 @@ + - - - + + + diff --git a/src/EventHub.Admin.Application.Contracts/EventHubAdminApplicationContractsModule.cs b/src/EventHub.Admin.Application.Contracts/EventHubAdminApplicationContractsModule.cs index d1cfef9..515a303 100644 --- a/src/EventHub.Admin.Application.Contracts/EventHubAdminApplicationContractsModule.cs +++ b/src/EventHub.Admin.Application.Contracts/EventHubAdminApplicationContractsModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Account; +using Payment.Admin; +using Volo.Abp.Account; using Volo.Abp.Identity; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; @@ -9,7 +10,8 @@ namespace EventHub.Admin typeof(EventHubDomainSharedModule), typeof(AbpAccountApplicationContractsModule), typeof(AbpIdentityApplicationContractsModule), - typeof(AbpPermissionManagementApplicationContractsModule) + typeof(AbpPermissionManagementApplicationContractsModule), + typeof(PaymentAdminApplicationContractsModule) )] public class EventHubAdminApplicationContractsModule : AbpModule { diff --git a/src/EventHub.Admin.Application.Contracts/Events/CountryLookupDto.cs b/src/EventHub.Admin.Application.Contracts/Events/CountryLookupDto.cs new file mode 100644 index 0000000..6056ea3 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/CountryLookupDto.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Events +{ + public class CountryLookupDto : EntityDto + { + public string Name { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs b/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs new file mode 100644 index 0000000..04b0c53 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/EventDetailDto.cs @@ -0,0 +1,28 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Events +{ + public class EventDetailDto : EntityDto + { + public string Title { get; set; } + + public string Description { get; set; } + + public DateTime StartTime { get; set; } + + public DateTime EndTime { get; set; } + + public bool IsOnline { get; set; } + + public string OnlineLink { get; set; } + + public Guid? CountryId { get; set; } + + public string City { get; set; } + + public string Language { get; set; } + + public int? Capacity { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/EventInListDto.cs b/src/EventHub.Admin.Application.Contracts/Events/EventInListDto.cs new file mode 100644 index 0000000..c4d148d --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/EventInListDto.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Events +{ + public class EventInListDto : EntityDto + { + public string Title { get; set; } + + public string OrganizationDisplayName { get; set; } + + public int AttendeeCount { get; set; } + + public DateTime StartTime { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/EventListFilterDto.cs b/src/EventHub.Admin.Application.Contracts/Events/EventListFilterDto.cs new file mode 100644 index 0000000..bf5cb36 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/EventListFilterDto.cs @@ -0,0 +1,15 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Events +{ + public class EventListFilterDto : PagedAndSortedResultRequestDto + { + public DateTime? MinStartTime { get; set; } + public DateTime? MaxStartTime { get; set; } + public string Title { get; set; } + public string OrganizationDisplayName { get; set; } + public int? MinAttendeeCount { get; set; } + public int? MaxAttendeeCount { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs b/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs new file mode 100644 index 0000000..da46c3b --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/IEventAppService.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Content; + +namespace EventHub.Admin.Events +{ + public interface IEventAppService : IApplicationService + { + Task> GetListAsync(EventListFilterDto input); + + Task GetAsync(Guid id); + + Task UpdateAsync(Guid id, UpdateEventDto input); + + Task> GetCountriesLookupAsync(); + + Task GetCoverImageAsync(Guid id); + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/Registrations/EventAttendeeDto.cs b/src/EventHub.Admin.Application.Contracts/Events/Registrations/EventAttendeeDto.cs new file mode 100644 index 0000000..60faf5a --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/Registrations/EventAttendeeDto.cs @@ -0,0 +1,17 @@ +using System; + +namespace EventHub.Admin.Events.Registrations +{ + public class EventAttendeeDto + { + public Guid UserId { get; set; } + + public string UserName { get; set; } + + public string Email { get; set; } + + public string Name { get; set; } + + public string Surname { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/Registrations/GetEventRegistrationListInput.cs b/src/EventHub.Admin.Application.Contracts/Events/Registrations/GetEventRegistrationListInput.cs new file mode 100644 index 0000000..8b07bf4 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/Registrations/GetEventRegistrationListInput.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Events.Registrations +{ + public class GetEventRegistrationListInput : PagedAndSortedResultRequestDto + { + public Guid EventId { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/Registrations/IEventRegistrationAppService.cs b/src/EventHub.Admin.Application.Contracts/Events/Registrations/IEventRegistrationAppService.cs new file mode 100644 index 0000000..eb491bb --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/Registrations/IEventRegistrationAppService.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace EventHub.Admin.Events.Registrations +{ + public interface IEventRegistrationAppService : IApplicationService + { + Task> GetAttendeesAsync(GetEventRegistrationListInput input); + + Task UnRegisterAttendeeAsync(Guid eventId, Guid attendeeId); + + Task RegisterUsersAsync(Guid eventId, List userIds); + + Task> GetAllAttendeeIdsAsync(Guid eventId); + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs b/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs new file mode 100644 index 0000000..e6c9d54 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Events/UpdateEventDto.cs @@ -0,0 +1,48 @@ +using System; +using System.ComponentModel.DataAnnotations; +using EventHub.Events; +using JetBrains.Annotations; +using Volo.Abp.Content; + +namespace EventHub.Admin.Events +{ + public class UpdateEventDto + { + [Required] + [StringLength(EventConsts.MaxTitleLength, MinimumLength = EventConsts.MinTitleLength)] + public string Title { get; set; } + + [Required] + [StringLength(EventConsts.MaxDescriptionLength, MinimumLength = EventConsts.MinDescriptionLength)] + public string Description { get; set; } + + [Required] + [DataType(DataType.DateTime)] + public DateTime StartTime { get; set; } + + [Required] + [DataType(DataType.DateTime)] + public DateTime EndTime { get; set; } + + [CanBeNull] + public RemoteStreamContent CoverImageStreamContent { get; set; } + + public bool IsOnline { get; set; } + + [CanBeNull] + [StringLength(EventConsts.MaxOnlineLinkLength, MinimumLength = EventConsts.MinOnlineLinkLength)] + public string OnlineLink { get; set; } + public Guid? CountryId { get; set; } + + [CanBeNull] + [StringLength(EventConsts.MaxCityLength, MinimumLength = EventConsts.MinCityLength)] + public string City { get; set; } + + [CanBeNull] + [StringLength(EventConsts.MaxLanguageLength, MinimumLength = EventConsts.MinLanguageLength)] + public string Language { get; set; } + + [Range(1, int.MaxValue)] + public int? Capacity { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs b/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs index b0850b8..fa26ed0 100644 --- a/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs +++ b/src/EventHub.Admin.Application.Contracts/Organizations/IOrganizationAppService.cs @@ -2,6 +2,7 @@ using System; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Content; namespace EventHub.Admin.Organizations { @@ -16,5 +17,7 @@ namespace EventHub.Admin.Organizations Task UpdateAsync(Guid id, UpdateOrganizationDto input); Task DeleteAsync(Guid id); + + Task GetCoverImageAsync(Guid id); } } diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs index 86b469c..bb7ad46 100644 --- a/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs +++ b/src/EventHub.Admin.Application.Contracts/Organizations/OrganizationProfileDto.cs @@ -9,8 +9,6 @@ namespace EventHub.Admin.Organizations public string DisplayName { get; set; } - public byte[] ProfilePictureContent { get; set; } - public string OwnerUserName { get; set; } public string OwnerEmail { get; set; } diff --git a/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs b/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs index c90c59b..e165910 100644 --- a/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs +++ b/src/EventHub.Admin.Application.Contracts/Organizations/UpdateOrganizationDto.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using EventHub.Organizations; using JetBrains.Annotations; +using Volo.Abp.Content; namespace EventHub.Admin.Organizations { @@ -15,7 +16,7 @@ namespace EventHub.Admin.Organizations public string Description { get; set; } [CanBeNull] - public byte[] ProfilePictureContent { get; set; } + public IRemoteStreamContent ProfilePictureStreamContent { get; set; } [CanBeNull] [StringLength(OrganizationConsts.MaxWebsiteLength)] diff --git a/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissionDefinitionProvider.cs b/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissionDefinitionProvider.cs index b9790b9..546274e 100644 --- a/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissionDefinitionProvider.cs +++ b/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissionDefinitionProvider.cs @@ -15,6 +15,15 @@ namespace EventHub.Admin.Permissions organizationsPermission.AddChild(EventHubPermissions.Organizations.Update, L("Permission:Edit")); organizationsPermission.AddChild(EventHubPermissions.Organizations.Delete, L("Permission:Delete")); organizationsPermission.AddChild(EventHubPermissions.Organizations.Memberships.Default, L("Permission:MembershipManagement")); + + var eventPermissions = eventHubGroup.AddPermission(EventHubPermissions.Events.Default, L("Permission:EventManagement")); + eventPermissions.AddChild(EventHubPermissions.Events.Update, L("Permission:Edit")); + + var eventRegistrationPermissions = eventHubGroup.AddPermission(EventHubPermissions.Events.Registrations.Default, L("Permission:RegistrationManagement")); + eventRegistrationPermissions.AddChild(EventHubPermissions.Events.Registrations.AddAttendee, L("Permission:AddAttendee")); + eventRegistrationPermissions.AddChild(EventHubPermissions.Events.Registrations.RemoveAttendee, L("Permission:RemoveAttendee")); + + var userPermissions = eventHubGroup.AddPermission(EventHubPermissions.Users.Default, L("Permission:UserManagement")); } private static LocalizableString L(string name) diff --git a/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissions.cs b/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissions.cs index a1003bc..f8d9f10 100644 --- a/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissions.cs +++ b/src/EventHub.Admin.Application.Contracts/Permissions/EventHubPermissions.cs @@ -16,5 +16,23 @@ public const string Default = Organizations.Default + ".Memberships"; } } + + public static class Events + { + public const string Default = GroupName + ".Events"; + public const string Update = Default + ".Update"; + + public class Registrations + { + public const string Default = Events.Default + ".Registrations"; + public const string AddAttendee = Default + ".AddAttendee"; + public const string RemoveAttendee = Default + ".RemoveAttendee"; + } + } + + public static class Users + { + public const string Default = GroupName + ".Users"; + } } } \ No newline at end of file diff --git a/src/EventHub.Admin.Application.Contracts/Users/GetUserListInput.cs b/src/EventHub.Admin.Application.Contracts/Users/GetUserListInput.cs new file mode 100644 index 0000000..9027b52 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Users/GetUserListInput.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Users +{ + public class GetUserListInput : PagedAndSortedResultRequestDto + { + public string Username { get; set; } + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Users/IUserAppService.cs b/src/EventHub.Admin.Application.Contracts/Users/IUserAppService.cs new file mode 100644 index 0000000..4920226 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Users/IUserAppService.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace EventHub.Admin.Users +{ + public interface IUserAppService : IApplicationService + { + Task> GetListAsync(GetUserListInput input); + } +} diff --git a/src/EventHub.Admin.Application.Contracts/Users/UserDto.cs b/src/EventHub.Admin.Application.Contracts/Users/UserDto.cs new file mode 100644 index 0000000..bffbed0 --- /dev/null +++ b/src/EventHub.Admin.Application.Contracts/Users/UserDto.cs @@ -0,0 +1,19 @@ +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; + +namespace EventHub.Admin.Users +{ + public class UserDto : EntityDto, IHasCreationTime + { + public string Username { get; set; } + + public string Email { get; set; } + + public string Name { get; set; } + + public string Surname { get; set; } + + public DateTime CreationTime { get; set; } + } +} diff --git a/src/EventHub.Admin.Application/EventHub.Admin.Application.csproj b/src/EventHub.Admin.Application/EventHub.Admin.Application.csproj index ee4b7b8..2dc0e91 100644 --- a/src/EventHub.Admin.Application/EventHub.Admin.Application.csproj +++ b/src/EventHub.Admin.Application/EventHub.Admin.Application.csproj @@ -3,19 +3,20 @@ - net5.0 + net6.0 EventHub.Admin + - - - + + + diff --git a/src/EventHub.Admin.Application/EventHubAdminApplicationModule.cs b/src/EventHub.Admin.Application/EventHubAdminApplicationModule.cs index 64fc7dc..82f293d 100644 --- a/src/EventHub.Admin.Application/EventHubAdminApplicationModule.cs +++ b/src/EventHub.Admin.Application/EventHubAdminApplicationModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Account; +using Payment.Admin; +using Volo.Abp.Account; using Volo.Abp.AutoMapper; using Volo.Abp.Identity; using Volo.Abp.Modularity; @@ -12,7 +13,8 @@ namespace EventHub.Admin typeof(EventHubAdminApplicationContractsModule), typeof(AbpIdentityApplicationModule), typeof(AbpPermissionManagementApplicationModule), - typeof(AbpAutoMapperModule) + typeof(AbpAutoMapperModule), + typeof(PaymentAdminApplicationModule) )] public class EventHubAdminApplicationModule : AbpModule { diff --git a/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs index b11a97c..f4a9ca2 100644 --- a/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs +++ b/src/EventHub.Admin.Application/EventHubApplicationAutoMapperProfile.cs @@ -1,13 +1,20 @@ using AutoMapper; +using EventHub.Admin.Events; +using EventHub.Admin.Events.Registrations; using EventHub.Admin.Organizations; using EventHub.Admin.Organizations.Memberships; +using EventHub.Admin.Users; +using EventHub.Countries; +using EventHub.Events; +using EventHub.Events.Registrations; using EventHub.Organizations; using EventHub.Organizations.Memberships; using Volo.Abp.AutoMapper; +using Volo.Abp.Identity; namespace EventHub.Admin { - public class EventHubAdminApplicationAutoMapperProfile : Profile + public class EventHubAdminApplicationAutoMapperProfile : Profile { public EventHubAdminApplicationAutoMapperProfile() { @@ -15,10 +22,23 @@ namespace EventHub.Admin CreateMap() .Ignore(x => x.OwnerUserName) - .Ignore(x => x.OwnerEmail) - .Ignore(x => x.ProfilePictureContent); + .Ignore(x => x.OwnerEmail); CreateMap(); + + CreateMap(); + + CreateMap(); + + CreateMap() + .ForMember(eventAttendee => eventAttendee.UserId, + opt => opt.MapFrom(user => user.Id)); + + CreateMap(); + + CreateMap(); + + CreateMap(); } } } diff --git a/src/EventHub.Admin.Application/Events/EventAppService.cs b/src/EventHub.Admin.Application/Events/EventAppService.cs new file mode 100644 index 0000000..0f58e09 --- /dev/null +++ b/src/EventHub.Admin.Application/Events/EventAppService.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using EventHub.Admin.Permissions; +using EventHub.Countries; +using EventHub.Events; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.BlobStoring; +using Volo.Abp.Content; +using Volo.Abp.Domain.Repositories; + +namespace EventHub.Admin.Events +{ + [Authorize(EventHubPermissions.Events.Default)] + public class EventAppService : EventHubAdminAppService, IEventAppService + { + private readonly IEventRepository _eventRepository; + private readonly IBlobContainer _eventBlobContainer; + private readonly EventManager _eventManager; + private readonly IRepository _countryRepository; + + public EventAppService( + IEventRepository eventRepository, + IBlobContainer eventBlobContainer, + EventManager eventManager, + IRepository countryRepository) + { + _eventRepository = eventRepository; + _eventBlobContainer = eventBlobContainer; + _eventManager = eventManager; + _countryRepository = countryRepository; + } + + public async Task GetAsync(Guid id) + { + var @event = await _eventRepository.GetAsync(id); + var eventDetailDto = ObjectMapper.Map(@event); + + return eventDetailDto; + } + + public async Task> GetListAsync(EventListFilterDto input) + { + var totalCount = await _eventRepository.GetCountAsync(input.Title, input.OrganizationDisplayName, input.MinAttendeeCount, + input.MaxAttendeeCount, input.MinStartTime, input.MaxStartTime); + + var items = await _eventRepository.GetListAsync(input.Sorting, input.SkipCount, input.MaxResultCount, + input.Title, input.OrganizationDisplayName, input.MinAttendeeCount, input.MaxAttendeeCount, + input.MinStartTime, input.MaxStartTime); + + var events = ObjectMapper.Map, List>(items); + + return new PagedResultDto(totalCount, events); + } + + [Authorize(EventHubPermissions.Events.Update)] + public async Task UpdateAsync(Guid id, UpdateEventDto input) + { + var @event = await _eventRepository.GetAsync(id); + + await _eventManager.SetLocationAsync(@event, input.IsOnline, input.OnlineLink, input.CountryId, input.City); + @event.SetTitle(input.Title); + @event.SetDescription(input.Description); + @event.Language = input.Language; + @event.SetTime(input.StartTime, @event.EndTime); + await _eventManager.SetCapacityAsync(@event, input.Capacity); + + if (input.CoverImageStreamContent != null && input.CoverImageStreamContent.ContentLength > 0) + { + await SetCoverImageAsync(blobName: id.ToString(), input.CoverImageStreamContent); + } + else + { + await DeleteCoverImageAsync(blobName: id.ToString()); + } + + await _eventRepository.UpdateAsync(@event); + } + + [AllowAnonymous] + public async Task GetCoverImageAsync(Guid id) + { + var blobName = id.ToString(); + var coverImageStream = await _eventBlobContainer.GetOrNullAsync(blobName); + + if (coverImageStream == null) + { + return null; + } + + return new RemoteStreamContent(coverImageStream); + } + + public async Task> GetCountriesLookupAsync() + { + var countriesQueryable = await _countryRepository.GetQueryableAsync(); + + var query = from country in countriesQueryable + orderby country.Name + select country; + + var countries = await AsyncExecuter.ToListAsync(query); + + return ObjectMapper.Map, List>(countries); + } + + private async Task SetCoverImageAsync(string blobName, IRemoteStreamContent streamContent, bool overrideExisting = true) + { + await _eventBlobContainer.SaveAsync(blobName, streamContent.GetStream(), overrideExisting); + } + + private async Task DeleteCoverImageAsync(string blobName) + { + await _eventBlobContainer.DeleteAsync(blobName); + } + } +} diff --git a/src/EventHub.Admin.Application/Events/Registrations/EventRegistrationAppService.cs b/src/EventHub.Admin.Application/Events/Registrations/EventRegistrationAppService.cs new file mode 100644 index 0000000..26495c5 --- /dev/null +++ b/src/EventHub.Admin.Application/Events/Registrations/EventRegistrationAppService.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading.Tasks; +using EventHub.Admin.Permissions; +using Volo.Abp.Application.Dtos; +using EventHub.Events.Registrations; +using Microsoft.AspNetCore.Authorization; +using EventHub.Events; +using EventHub.Users; + +namespace EventHub.Admin.Events.Registrations +{ + [Authorize(EventHubPermissions.Events.Registrations.Default)] + public class EventRegistrationAppService : EventHubAdminAppService, IEventRegistrationAppService + { + private readonly IUserRepository _userRepository; + private readonly IEventRegistrationRepository _eventRegistrationRepository; + private readonly IEventRepository _eventRepository; + private readonly EventRegistrationManager _eventRegistrationManager; + + public EventRegistrationAppService( + IUserRepository userRepository, + IEventRegistrationRepository eventRegistrationRepository, + IEventRepository eventRepository, + EventRegistrationManager eventRegistrationManager) + { + _userRepository = userRepository; + _eventRegistrationRepository = eventRegistrationRepository; + _eventRepository = eventRepository; + _eventRegistrationManager = eventRegistrationManager; + } + + public async Task> GetAttendeesAsync(GetEventRegistrationListInput input) + { + var totalCount = await _eventRegistrationRepository.GetCountAsync(input.EventId); + var items = await _eventRegistrationRepository.GetListAsync(input.EventId, input.Sorting, input.SkipCount, input.MaxResultCount); + var users = ObjectMapper.Map, List>(items); + + return new PagedResultDto(totalCount, users); + } + + [Authorize(EventHubPermissions.Events.Registrations.RemoveAttendee)] + public async Task UnRegisterAttendeeAsync(Guid eventId, Guid attendeeId) + { + await _eventRegistrationRepository.DeleteAsync(x => x.EventId == eventId && x.UserId == attendeeId); + } + + [Authorize(EventHubPermissions.Events.Registrations.AddAttendee)] + public async Task RegisterUsersAsync(Guid eventId, List userIds) + { + if (userIds == null || !userIds.Any()) + { + return; + } + + var @event = await _eventRepository.GetAsync(eventId); + + var userQueryable = await _userRepository.GetQueryableAsync(); + var query = userQueryable.Where(user => userIds.Contains(user.Id)); + + var users = await AsyncExecuter.ToListAsync(query); + foreach (var user in users) + { + await _eventRegistrationManager.RegisterAsync(@event, user); + } + } + + public async Task> GetAllAttendeeIdsAsync(Guid eventId) + { + var eventRegistrationQueryable = await _eventRegistrationRepository.GetQueryableAsync(); + var userQueryable = await _userRepository.GetQueryableAsync(); + + var query = from eventRegistration in eventRegistrationQueryable + join user in userQueryable on eventRegistration.UserId equals user.Id + where eventRegistration.EventId == eventId + orderby eventRegistration.CreationTime descending + select user.Id; + + return await AsyncExecuter.ToListAsync(query); + } + } +} diff --git a/src/EventHub.Admin.Application/Organizations/Memberships/OrganizationMembershipAppService.cs b/src/EventHub.Admin.Application/Organizations/Memberships/OrganizationMembershipAppService.cs index b97d381..afba9ec 100644 --- a/src/EventHub.Admin.Application/Organizations/Memberships/OrganizationMembershipAppService.cs +++ b/src/EventHub.Admin.Application/Organizations/Memberships/OrganizationMembershipAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using EventHub.Admin.Permissions; using EventHub.Organizations.Memberships; +using EventHub.Users; using Microsoft.AspNetCore.Authorization; using Volo.Abp; using Volo.Abp.Application.Dtos; @@ -17,11 +18,11 @@ namespace EventHub.Admin.Organizations.Memberships public class OrganizationMembershipAppService : ApplicationService, IOrganizationMembershipAppService { private readonly IOrganizationMembershipRepository _organizationMembershipRepository; - private readonly IRepository _userRepository; + private readonly IUserRepository _userRepository; public OrganizationMembershipAppService( - IOrganizationMembershipRepository organizationMembershipRepository, - IRepository userRepository) + IOrganizationMembershipRepository organizationMembershipRepository, + IUserRepository userRepository) { _organizationMembershipRepository = organizationMembershipRepository; _userRepository = userRepository; diff --git a/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs b/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs index df3794c..ba4049f 100644 --- a/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs +++ b/src/EventHub.Admin.Application/Organizations/OrganizationAppService.cs @@ -5,12 +5,13 @@ using System.Linq.Dynamic.Core; using System.Threading.Tasks; using EventHub.Admin.Permissions; using EventHub.Organizations; +using EventHub.Users; using Microsoft.AspNetCore.Authorization; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.BlobStoring; +using Volo.Abp.Content; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Identity; namespace EventHub.Admin.Organizations { @@ -18,12 +19,12 @@ namespace EventHub.Admin.Organizations public class OrganizationAppService : EventHubAdminAppService, IOrganizationAppService { private readonly IRepository _organizationRepository; - private readonly IRepository _identityUserRepository; + private readonly IUserRepository _identityUserRepository; private readonly IBlobContainer _organizationBlobContainer; public OrganizationAppService( IRepository organizationRepository, - IRepository identityUserRepository, + IUserRepository identityUserRepository, IBlobContainer organizationBlobContainer) { _organizationRepository = organizationRepository; @@ -77,7 +78,7 @@ namespace EventHub.Admin.Organizations return await CreateOrganizationProfileDto(organization); } - + public async Task GetByNameAsync(string name) { var organization = await _organizationRepository.FindAsync(x => x.Name.ToLower() == name.ToLower()); @@ -105,13 +106,13 @@ namespace EventHub.Admin.Organizations organization.InstagramUsername = input.InstagramUsername; organization.MediumUsername = input.MediumUsername; - if (input.ProfilePictureContent?.Length > 0) + if (input.ProfilePictureStreamContent != null && input.ProfilePictureStreamContent.ContentLength > 0) { - await SaveCoverImageAsync(organization.Id, input.ProfilePictureContent); + await SaveCoverImageAsync(organization.Id, input.ProfilePictureStreamContent); } else { - await DeleteCoverImageAsync(organization.Id); + await DeleteCoverImageAsync(blobName: id.ToString()); } await _organizationRepository.UpdateAsync(organization); @@ -124,7 +125,7 @@ namespace EventHub.Admin.Organizations { await _organizationRepository.DeleteAsync(id); } - + private async Task CreateOrganizationProfileDto(Organization organization) { var dto = ObjectMapper.Map(organization); @@ -133,28 +134,32 @@ namespace EventHub.Admin.Organizations dto.OwnerUserName = user.UserName; dto.OwnerEmail = user.Email; - dto.ProfilePictureContent = await GetCoverImageAsync(organization.Id); return dto; } - private async Task GetCoverImageAsync(Guid id) + [AllowAnonymous] + public async Task GetCoverImageAsync(Guid id) { var blobName = id.ToString(); + var coverImageStream = await _organizationBlobContainer.GetOrNullAsync(blobName); - return await _organizationBlobContainer.GetAllBytesOrNullAsync(blobName); + if (coverImageStream is null) + { + return null; + } + + return new RemoteStreamContent(coverImageStream, blobName); } - private async Task SaveCoverImageAsync(Guid id, byte[] coverImageContent) + private async Task SaveCoverImageAsync(Guid id, IRemoteStreamContent coverImageContent) { var blobName = id.ToString(); - await _organizationBlobContainer.SaveAsync(blobName, coverImageContent, overrideExisting: true); + await _organizationBlobContainer.SaveAsync(blobName, coverImageContent.GetStream(), overrideExisting: true); } - private async Task DeleteCoverImageAsync(Guid id) + private async Task DeleteCoverImageAsync(string blobName) { - var blobName = id.ToString(); - await _organizationBlobContainer.DeleteAsync(blobName); } } diff --git a/src/EventHub.Admin.Application/Users/UserAppService.cs b/src/EventHub.Admin.Application/Users/UserAppService.cs new file mode 100644 index 0000000..8af8994 --- /dev/null +++ b/src/EventHub.Admin.Application/Users/UserAppService.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using EventHub.Admin.Permissions; +using EventHub.Users; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Identity; + +namespace EventHub.Admin.Users +{ + [Authorize(EventHubPermissions.Users.Default)] + public class UserAppService : EventHubAdminAppService, IUserAppService + { + private readonly IUserRepository _userRepository; + + public UserAppService(IUserRepository userRepository) + { + _userRepository = userRepository; + } + + public async Task> GetListAsync(GetUserListInput input) + { + var totalCount = await _userRepository.GetCountAsync(input.Username); + var items = await _userRepository.GetListAsync(input.Sorting, input.SkipCount, input.MaxResultCount, input.Username); + var users = ObjectMapper.Map, List>(items); + + return new PagedResultDto(totalCount, users); + } + } +} diff --git a/src/EventHub.Admin.HttpApi.Client/EventHub.Admin.HttpApi.Client.csproj b/src/EventHub.Admin.HttpApi.Client/EventHub.Admin.HttpApi.Client.csproj index 9a45bce..970910c 100644 --- a/src/EventHub.Admin.HttpApi.Client/EventHub.Admin.HttpApi.Client.csproj +++ b/src/EventHub.Admin.HttpApi.Client/EventHub.Admin.HttpApi.Client.csproj @@ -9,12 +9,13 @@ + - - - + + + diff --git a/src/EventHub.Admin.HttpApi.Client/EventHubAdminHttpApiClientModule.cs b/src/EventHub.Admin.HttpApi.Client/EventHubAdminHttpApiClientModule.cs index 0fff8ac..79ec5ae 100644 --- a/src/EventHub.Admin.HttpApi.Client/EventHubAdminHttpApiClientModule.cs +++ b/src/EventHub.Admin.HttpApi.Client/EventHubAdminHttpApiClientModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Payment.Admin; using Volo.Abp.Account; using Volo.Abp.Identity; using Volo.Abp.Modularity; @@ -10,17 +11,16 @@ namespace EventHub.Admin typeof(EventHubAdminApplicationContractsModule), typeof(AbpAccountHttpApiClientModule), typeof(AbpIdentityHttpApiClientModule), - typeof(AbpPermissionManagementHttpApiClientModule) + typeof(AbpPermissionManagementHttpApiClientModule), + typeof(PaymentAdminHttpApiClientModule) )] public class EventHubAdminHttpApiClientModule : AbpModule { - public const string RemoteServiceName = "Default"; - public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddHttpClientProxies( typeof(EventHubAdminApplicationContractsModule).Assembly, - RemoteServiceName + EventHubAdminRemoteServiceConsts.RemoteServiceName ); } } diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs new file mode 100644 index 0000000..10ddbe8 --- /dev/null +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/Events/EventController.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using EventHub.Admin.Events; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Content; + +namespace EventHub.Admin.Controllers.Events +{ + [RemoteService(Name = EventHubAdminRemoteServiceConsts.RemoteServiceName)] + [Controller] + [Area("eventhub-admin")] + [ControllerName("Event")] + [Route("/api/eventhub/admin/event")] + public class EventController : AbpController, IEventAppService + { + private readonly IEventAppService _eventAppService; + + public EventController(IEventAppService eventAppService) + { + _eventAppService = eventAppService; + } + + [HttpGet("{id}")] + public Task GetAsync(Guid id) + { + return _eventAppService.GetAsync(id); + } + + [HttpGet("countries")] + public Task> GetCountriesLookupAsync() + { + return _eventAppService.GetCountriesLookupAsync(); + } + + [HttpGet("cover-image/{id}")] + [AllowAnonymous] + public async Task GetCoverImageAsync(Guid id) + { + var remoteStreamContent = await _eventAppService.GetCoverImageAsync(id); + if (remoteStreamContent is null) + { + return null; + } + + Response.Headers.Add("Accept-Ranges", "bytes"); + Response.ContentType = remoteStreamContent.ContentType; + + return remoteStreamContent; + } + + [HttpGet] + public Task> GetListAsync(EventListFilterDto input) + { + return _eventAppService.GetListAsync(input); + } + + [HttpPut] + public Task UpdateAsync(Guid id, [FromForm] UpdateEventDto input) + { + return _eventAppService.UpdateAsync(id, input); + } + } +} diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/Events/Registrations/EventRegistrationController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/Events/Registrations/EventRegistrationController.cs new file mode 100644 index 0000000..7abd373 --- /dev/null +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/Events/Registrations/EventRegistrationController.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using EventHub.Admin.Events.Registrations; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc; + +namespace EventHub.Admin.Controllers.Events.Registrations +{ + [RemoteService(Name = EventHubAdminRemoteServiceConsts.RemoteServiceName)] + [Controller] + [Area("eventhub-admin")] + [ControllerName("EventRegistration")] + [Route("/api/eventhub/admin/event-registration")] + public class EventRegistrationController : AbpController, IEventRegistrationAppService + { + private readonly IEventRegistrationAppService _eventRegistrationAppService; + + public EventRegistrationController(IEventRegistrationAppService eventRegistrationAppService) + { + _eventRegistrationAppService = eventRegistrationAppService; + } + + [HttpGet("{eventId}")] + public Task> GetAllAttendeeIdsAsync(Guid eventId) + { + return _eventRegistrationAppService.GetAllAttendeeIdsAsync(eventId); + } + + [HttpGet] + public Task> GetAttendeesAsync(GetEventRegistrationListInput input) + { + return _eventRegistrationAppService.GetAttendeesAsync(input); + } + + [HttpPost] + public Task RegisterUsersAsync(Guid eventId, List userIds) + { + return _eventRegistrationAppService.RegisterUsersAsync(eventId, userIds); + } + + [HttpDelete] + public Task UnRegisterAttendeeAsync(Guid eventId, Guid attendeeId) + { + return _eventRegistrationAppService.UnRegisterAttendeeAsync(eventId, attendeeId); + } + } +} diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs index 82fd290..af795db 100644 --- a/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/Organizations/OrganizationController.cs @@ -1,10 +1,12 @@ using System; using System.Threading.Tasks; using EventHub.Admin.Organizations; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Content; namespace EventHub.Admin.Controllers.Organizations { @@ -16,7 +18,7 @@ namespace EventHub.Admin.Controllers.Organizations public class OrganizationController : AbpController, IOrganizationAppService { private readonly IOrganizationAppService _organizationAppService; - + public OrganizationController(IOrganizationAppService organizationAppService) { _organizationAppService = organizationAppService; @@ -43,7 +45,7 @@ namespace EventHub.Admin.Controllers.Organizations } [HttpPut] - public Task UpdateAsync(Guid id, UpdateOrganizationDto input) + public Task UpdateAsync(Guid id, [FromForm] UpdateOrganizationDto input) { return _organizationAppService.UpdateAsync(id, input); } @@ -54,5 +56,22 @@ namespace EventHub.Admin.Controllers.Organizations { return _organizationAppService.DeleteAsync(id); } + + [HttpGet] + [AllowAnonymous] + [Route("cover-image/{id}")] + public async Task GetCoverImageAsync(Guid id) + { + var remoteStreamContent = await _organizationAppService.GetCoverImageAsync(id); + if (remoteStreamContent is null) + { + return null; + } + + Response.Headers.Add("Accept-Ranges", "bytes"); + Response.ContentType = remoteStreamContent.ContentType; + + return remoteStreamContent; + } } } \ No newline at end of file diff --git a/src/EventHub.Admin.HttpApi.Host/Controllers/Users/UserController.cs b/src/EventHub.Admin.HttpApi.Host/Controllers/Users/UserController.cs new file mode 100644 index 0000000..75ff77e --- /dev/null +++ b/src/EventHub.Admin.HttpApi.Host/Controllers/Users/UserController.cs @@ -0,0 +1,30 @@ +using EventHub.Admin.Users; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc; + +namespace EventHub.Admin.Controllers.Users +{ + [RemoteService(Name = EventHubAdminRemoteServiceConsts.RemoteServiceName)] + [Controller] + [Area("eventhub-admin")] + [ControllerName("User")] + [Route("/api/eventhub/admin/user")] + public class UserController : AbpController, IUserAppService + { + private readonly IUserAppService _userAppService; + + public UserController(IUserAppService userAppService) + { + _userAppService = userAppService; + } + + [HttpGet] + public Task> GetListAsync(GetUserListInput input) + { + return _userAppService.GetListAsync(input); + } + } +} diff --git a/src/EventHub.Admin.HttpApi.Host/Dockerfile b/src/EventHub.Admin.HttpApi.Host/Dockerfile index 7dda575..b272c02 100644 --- a/src/EventHub.Admin.HttpApi.Host/Dockerfile +++ b/src/EventHub.Admin.HttpApi.Host/Dockerfile @@ -1,4 +1,4 @@ - FROM mcr.microsoft.com/dotnet/aspnet:5.0 - COPY bin/Release/net5.0/publish/ app/ + FROM mcr.microsoft.com/dotnet/aspnet:6.0.0-bullseye-slim + COPY bin/Release/net6.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "EventHub.Admin.HttpApi.Host.dll"] \ No newline at end of file diff --git a/src/EventHub.Admin.HttpApi.Host/EventHub.Admin.HttpApi.Host.csproj b/src/EventHub.Admin.HttpApi.Host/EventHub.Admin.HttpApi.Host.csproj index f67494c..24e8327 100644 --- a/src/EventHub.Admin.HttpApi.Host/EventHub.Admin.HttpApi.Host.csproj +++ b/src/EventHub.Admin.HttpApi.Host/EventHub.Admin.HttpApi.Host.csproj @@ -3,7 +3,7 @@ - net5.0 + net6.0 EventHub.Admin true EventHub-4681b4fd-151f-4221-84a4-929d86723e4c @@ -12,14 +12,14 @@ - - - - - - - - + + + + + + + + diff --git a/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs b/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs index a2b47f8..7bb8d9b 100644 --- a/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs +++ b/src/EventHub.Admin.HttpApi.Host/EventHubAdminHttpApiHostModule.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using EventHub.Admin.Events; +using EventHub.Admin.Organizations; using EventHub.Admin.Utils; using EventHub.EntityFrameworkCore; using EventHub.Web; @@ -17,6 +19,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; using StackExchange.Redis; using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; @@ -59,6 +62,16 @@ namespace EventHub.Admin ConfigureCookies(context); ConfigureSwaggerServices(context, configuration); ConfigureBackgroundJobs(); + ConfigureAutoApiControllers(); + } + + private void ConfigureAutoApiControllers() + { + Configure(options => + { + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(UpdateOrganizationDto)); + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(UpdateEventDto)); + }); } private void ConfigureBackgroundJobs() @@ -78,9 +91,9 @@ namespace EventHub.Admin { var hostingEnvironment = context.Services.GetHostingEnvironment(); - if (hostingEnvironment.IsDevelopment()) + Configure(options => { - Configure(options => + if (hostingEnvironment.IsDevelopment()) { options.FileSets.ReplaceEmbeddedByPhysical( Path.Combine(hostingEnvironment.ContentRootPath, @@ -94,8 +107,8 @@ namespace EventHub.Admin options.FileSets.ReplaceEmbeddedByPhysical( Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}EventHub.Admin.Application")); - }); - } + } + }); } private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) diff --git a/src/EventHub.Admin.HttpApi/EventHub.Admin.HttpApi.csproj b/src/EventHub.Admin.HttpApi/EventHub.Admin.HttpApi.csproj index 5fdd465..bd8b091 100644 --- a/src/EventHub.Admin.HttpApi/EventHub.Admin.HttpApi.csproj +++ b/src/EventHub.Admin.HttpApi/EventHub.Admin.HttpApi.csproj @@ -3,18 +3,19 @@ - net5.0 + net6.0 EventHub.Admin + - - - + + + diff --git a/src/EventHub.Admin.HttpApi/EventHubAdminHttpApiModule.cs b/src/EventHub.Admin.HttpApi/EventHubAdminHttpApiModule.cs index c5cb1e2..d12aed7 100644 --- a/src/EventHub.Admin.HttpApi/EventHubAdminHttpApiModule.cs +++ b/src/EventHub.Admin.HttpApi/EventHubAdminHttpApiModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Account; +using Payment.Admin; +using Volo.Abp.Account; using Volo.Abp.Identity; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.HttpApi; @@ -9,7 +10,8 @@ namespace EventHub.Admin typeof(EventHubAdminApplicationContractsModule), typeof(AbpAccountHttpApiModule), typeof(AbpIdentityHttpApiModule), - typeof(AbpPermissionManagementHttpApiModule) + typeof(AbpPermissionManagementHttpApiModule), + typeof(PaymentAdminHttpApiModule) )] public class EventHubAdminHttpApiModule : AbpModule { diff --git a/src/EventHub.Admin.Web/Components/UserPicker/UserPicker.razor b/src/EventHub.Admin.Web/Components/UserPicker/UserPicker.razor new file mode 100644 index 0000000..30a80ac --- /dev/null +++ b/src/EventHub.Admin.Web/Components/UserPicker/UserPicker.razor @@ -0,0 +1,71 @@ +@using EventHub.Admin.Users +@inherits EventHubComponentBase +@inject IUserAppService UserAppService + + + + + @L["AddUser"] + + + + + + + @L["UserName"] + + + + + + + + + + + + + @if (SelectAllUsers.ContainsKey(context.Id)) + { + + } + + + + + + @(context.Username) + + + + + + @(context.Name) + + + + + + @(context.Surname) + + + + + + @(context.Email) + + + + + + + @L["Cancel"] + @L["Save"] + + + \ No newline at end of file diff --git a/src/EventHub.Admin.Web/Components/UserPicker/UserPicker.razor.cs b/src/EventHub.Admin.Web/Components/UserPicker/UserPicker.razor.cs new file mode 100644 index 0000000..f8469db --- /dev/null +++ b/src/EventHub.Admin.Web/Components/UserPicker/UserPicker.razor.cs @@ -0,0 +1,128 @@ +using Blazorise; +using EventHub.Admin.Users; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazorise.DataGrid; +using Microsoft.AspNetCore.Components.Web; +using Volo.Abp.Application.Dtos; + +namespace EventHub.Admin.Web.Components.UserPicker +{ + public partial class UserPicker + { + public Modal UserPickerModal { get; set; } + + [Parameter] + public EventCallback SaveFormAsync { get; set; } + + [Parameter] + public List SelectedUserIds { get; set; } = new List(); + + private GetUserListInput Filter { get; set; } + + private IReadOnlyList UserList { get; set; } + private int CurrentPage { get; set; } + private string CurrentSorting { get; set; } + private int TotalCount { get; set; } + private int PageSize { get; } + + public Dictionary SelectAllUsers = new(); + private bool AllUserSelected + { + get => SelectAllUsers.All(x => x.Value); + set + { + foreach (var key in SelectAllUsers.Keys) + { + SelectAllUsers[key] = value; + } + } + } + + public UserPicker() + { + Filter = new GetUserListInput(); + PageSize = LimitedResultRequestDto.DefaultMaxResultCount; + } + + protected override async Task OnInitializedAsync() + { + await GetUsersAsync(); + } + + protected override async Task OnParametersSetAsync() + { + await GetUsersAsync(); + } + + private async Task GetUsersAsync() + { + Filter.MaxResultCount = PageSize; + Filter.SkipCount = CurrentPage * PageSize; + Filter.Sorting = CurrentSorting; + + var result = await UserAppService.GetListAsync(Filter); + UserList = result.Items; + TotalCount = (int)result.TotalCount; + + FillSelectedUserList(); + } + + private void FillSelectedUserList() + { + SelectAllUsers = new Dictionary(); + + foreach (var user in UserList) + { + SelectAllUsers.Add(user.Id, SelectedUserIds.Contains(user.Id)); + } + } + + private async Task OnKeyPressed(KeyboardEventArgs e) + { + if (e.Code is "Enter" or "NumpadEnter") + { + await GetUsersAsync(); + } + } + + private async Task OnDataGridReadAsync(DataGridReadDataEventArgs e) + { + CurrentSorting = e.Columns + .Where(c => c.SortDirection != SortDirection.None) + .Select(c => c.Field + (c.SortDirection == SortDirection.Descending ? " DESC" : "")) + .JoinAsString(","); + CurrentPage = e.Page - 1; + + await GetUsersAsync(); + await InvokeAsync(StateHasChanged); + } + + public Task OpenUserPickerModalAsync() + { + UserPickerModal.Show(); + + return Task.CompletedTask; + } + + public Task CloseUserPickerModalAsync() + { + UserPickerModal.Hide(); + + return Task.CompletedTask; + } + + private async Task SaveUserPickerFormAsync() + { + await SaveFormAsync.InvokeAsync(); + } + + private void ClosingUserPickerModal(ModalClosingEventArgs eventArgs) + { + eventArgs.Cancel = eventArgs.CloseReason == CloseReason.FocusLostClosing; + } + } +} diff --git a/src/EventHub.Admin.Web/Dockerfile b/src/EventHub.Admin.Web/Dockerfile index cdfeb3c..a4d84a4 100644 --- a/src/EventHub.Admin.Web/Dockerfile +++ b/src/EventHub.Admin.Web/Dockerfile @@ -1,3 +1,3 @@ FROM nginx:latest -COPY ./bin/Release/net5.0/publish/wwwroot/ /usr/share/nginx/html/ +COPY ./bin/Release/net6.0/publish/wwwroot/ /usr/share/nginx/html/ COPY ./nginx.conf /etc/nginx/conf.d/default.conf \ No newline at end of file diff --git a/src/EventHub.Admin.Web/EventHub.Admin.Web.csproj b/src/EventHub.Admin.Web/EventHub.Admin.Web.csproj index aaa7547..02e0418 100644 --- a/src/EventHub.Admin.Web/EventHub.Admin.Web.csproj +++ b/src/EventHub.Admin.Web/EventHub.Admin.Web.csproj @@ -1,26 +1,29 @@ - net5.0 + net6.0 true - false + - - - - + + + + - - - + + + + + + diff --git a/src/EventHub.Admin.Web/EventHubBlazorAutoMapperProfile.cs b/src/EventHub.Admin.Web/EventHubBlazorAutoMapperProfile.cs index 53cffe5..f55858c 100644 --- a/src/EventHub.Admin.Web/EventHubBlazorAutoMapperProfile.cs +++ b/src/EventHub.Admin.Web/EventHubBlazorAutoMapperProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using EventHub.Admin.Events; using EventHub.Admin.Organizations; namespace EventHub.Admin.Web @@ -8,6 +9,8 @@ namespace EventHub.Admin.Web public EventHubBlazorAutoMapperProfile() { CreateMap(); + + CreateMap(); } } } \ No newline at end of file diff --git a/src/EventHub.Admin.Web/EventHubBlazorModule.cs b/src/EventHub.Admin.Web/EventHubBlazorModule.cs index e0a01ff..d9b266a 100644 --- a/src/EventHub.Admin.Web/EventHubBlazorModule.cs +++ b/src/EventHub.Admin.Web/EventHubBlazorModule.cs @@ -7,6 +7,7 @@ using IdentityModel; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Payment.Admin; using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme; @@ -22,7 +23,8 @@ namespace EventHub.Admin.Web typeof(AbpAutofacWebAssemblyModule), typeof(EventHubAdminHttpApiClientModule), typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule), - typeof(AbpIdentityBlazorWebAssemblyModule) + typeof(AbpIdentityBlazorWebAssemblyModule), + typeof(PaymentAdminBlazorModule) )] public class EventHubBlazorModule : AbpModule { diff --git a/src/EventHub.Admin.Web/Menus/EventHubMenuContributor.cs b/src/EventHub.Admin.Web/Menus/EventHubMenuContributor.cs index 1aa0481..921d882 100644 --- a/src/EventHub.Admin.Web/Menus/EventHubMenuContributor.cs +++ b/src/EventHub.Admin.Web/Menus/EventHubMenuContributor.cs @@ -4,7 +4,6 @@ using EventHub.Admin.Permissions; using EventHub.Localization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; using Volo.Abp.Account.Localization; using Volo.Abp.Authorization.Permissions; using Volo.Abp.UI.Navigation; @@ -33,21 +32,43 @@ namespace EventHub.Admin.Web.Menus } } - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + private Task ConfigureMainMenuAsync(MenuConfigurationContext context) { var l = context.GetLocalizer(); - context.Menu.Items.Insert( - 0, + var eventHubMenuItem = new ApplicationMenuItem( + EventHubMenus.Prefix, + "EventHub" + ); + + context.Menu.Items.Insert(0, eventHubMenuItem); + + eventHubMenuItem.AddItem( new ApplicationMenuItem( - EventHubMenus.Home, - l["Menu:Home"], - "/", - icon: "fas fa-home" - ) + EventHubMenus.OrganizationManagement.Organizations, + l["Menu:Organizations"], + url: "/organizations" + ).RequirePermissions(EventHubPermissions.Organizations.Default) ); - await AddOrganizationMenu(context, l); + eventHubMenuItem.AddItem( + new ApplicationMenuItem( + EventHubMenus.OrganizationManagement.OrganizationMemberships, + l["Menu:OrganizationMemberships"], + url: "/organization-memberships" + ).RequirePermissions(EventHubPermissions.Organizations.Memberships.Default) + ); + + eventHubMenuItem + .AddItem( + new ApplicationMenuItem( + EventHubMenus.EventManagement.Events, + displayName: l["Menu:Events"], + url: "/events" + ).RequirePermissions(EventHubPermissions.Events.Default) + ); + + return Task.CompletedTask; } private Task ConfigureUserMenuAsync(MenuConfigurationContext context) @@ -61,7 +82,7 @@ namespace EventHub.Admin.Web.Menus { context.Menu.AddItem(new ApplicationMenuItem( "Account.Manage", - accountStringLocalizer["ManageYourProfile"], + accountStringLocalizer["Manage"], $"{identityServerUrl.EnsureEndsWith('/')}Account/Manage?returnUrl={_configuration["App:SelfUrl"]}", icon: "fa fa-cog", order: 1000, @@ -70,22 +91,5 @@ namespace EventHub.Admin.Web.Menus return Task.CompletedTask; } - - private Task AddOrganizationMenu(MenuConfigurationContext context, IStringLocalizer l) - { - var organizationMenu = new ApplicationMenuItem(EventHubMenus.OrganizationManagement.GroupName, - l["Menu:OrganizationManagement"], icon: "fa fa-sitemap"); - context.Menu.Items.Insert(2, organizationMenu); - - organizationMenu.AddItem( - new ApplicationMenuItem(EventHubMenus.OrganizationManagement.Organizations, l["Menu:Organizations"], - url: "/organizations").RequirePermissions(EventHubPermissions.Organizations.Default)); - organizationMenu.AddItem( - new ApplicationMenuItem(EventHubMenus.OrganizationManagement.OrganizationMemberships, - l["Menu:OrganizationMemberships"], url: "/organization-memberships") - .RequirePermissions(EventHubPermissions.Organizations.Memberships.Default)); - - return Task.CompletedTask; - } } } \ No newline at end of file diff --git a/src/EventHub.Admin.Web/Menus/EventHubMenus.cs b/src/EventHub.Admin.Web/Menus/EventHubMenus.cs index 66562a1..f123f2b 100644 --- a/src/EventHub.Admin.Web/Menus/EventHubMenus.cs +++ b/src/EventHub.Admin.Web/Menus/EventHubMenus.cs @@ -2,8 +2,7 @@ { public class EventHubMenus { - private const string Prefix = "EventHub"; - public const string Home = Prefix + ".Home"; + public const string Prefix = "EventHub"; public static class OrganizationManagement { @@ -11,5 +10,11 @@ public const string Organizations = GroupName + ".Organizations"; public const string OrganizationMemberships = GroupName + ".OrganizationMemberships"; } + + public static class EventManagement + { + public const string GroupName = Prefix + ".EventManagement"; + public const string Events = GroupName + ".Events"; + } } } \ No newline at end of file diff --git a/src/EventHub.Admin.Web/Pages/AttendeeDetail.razor b/src/EventHub.Admin.Web/Pages/AttendeeDetail.razor new file mode 100644 index 0000000..c9061d4 --- /dev/null +++ b/src/EventHub.Admin.Web/Pages/AttendeeDetail.razor @@ -0,0 +1,68 @@ +@page "/event/{EventId:guid}/attendees" +@using EventHub.Admin.Web.Components.UserPicker +@using EventHub.Admin.Events.Registrations +@using EventHub.Admin.Permissions +@using EventHub.Admin.Users +@inherits EventHubComponentBase +@inject IEventRegistrationAppService EventRegistrationAppService +@inject IUserAppService UserAppService + + + + @L["Attendees"] + + + @if (CanAddAttendee) + { + + @L["AddNewUser"] + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/EventHub.Admin.Web/Pages/AttendeeDetail.razor.cs b/src/EventHub.Admin.Web/Pages/AttendeeDetail.razor.cs new file mode 100644 index 0000000..4bf9baa --- /dev/null +++ b/src/EventHub.Admin.Web/Pages/AttendeeDetail.razor.cs @@ -0,0 +1,113 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Threading.Tasks; +using EventHub.Admin.Events.Registrations; +using System.Collections.Generic; +using System.Linq; +using Blazorise; +using Volo.Abp.Application.Dtos; +using Blazorise.DataGrid; +using EventHub.Admin.Permissions; +using EventHub.Admin.Web.Components.UserPicker; +using Microsoft.AspNetCore.Authorization; + +namespace EventHub.Admin.Web.Pages +{ + public partial class AttendeeDetail + { + [Parameter] + public Guid EventId { get; set; } + private IReadOnlyList AttendeeList { get; set; } + private int CurrentPage { get; set; } + private string CurrentSorting { get; set; } + private int TotalCount { get; set; } + private int PageSize { get; } + private bool CanAddAttendee { get; set; } + private GetEventRegistrationListInput Filter { get; set; } + + public UserPicker UserPickerModalRef { get; set; } + private List SelectedUserIds { get; set; } + public AttendeeDetail() + { + PageSize = LimitedResultRequestDto.DefaultMaxResultCount; + Filter = new GetEventRegistrationListInput(); + SelectedUserIds = new List(); + } + + protected override async Task OnInitializedAsync() + { + await SetPermissionsAsync(); + await GetAttendeesAsync(); + } + + private async Task GetAttendeesAsync() + { + Filter.EventId = EventId; + Filter.MaxResultCount = PageSize; + Filter.SkipCount = CurrentPage * PageSize; + Filter.Sorting = CurrentSorting; + + var result = await EventRegistrationAppService.GetAttendeesAsync(Filter); + + AttendeeList = result.Items; + TotalCount = (int)result.TotalCount; + + SelectedUserIds = await EventRegistrationAppService.GetAllAttendeeIdsAsync(EventId); + } + + private async Task SetPermissionsAsync() + { + CanAddAttendee = await AuthorizationService.IsGrantedAsync(EventHubPermissions.Events.Registrations.AddAttendee); + } + + private async Task OnDataGridReadAsync(DataGridReadDataEventArgs e) + { + CurrentSorting = e.Columns + .Where(c => c.SortDirection != SortDirection.None) + .Select(c => c.Field + (c.SortDirection == SortDirection.Descending ? " DESC" : "")) + .JoinAsString(","); + CurrentPage = e.Page - 1; + + await GetAttendeesAsync(); + await InvokeAsync(StateHasChanged); + } + private async Task RemoveAttendeeAsync(EventAttendeeDto attendee) + { + await EventRegistrationAppService.UnRegisterAttendeeAsync(EventId, attendee.UserId); + await GetAttendeesAsync(); + } + + private async Task AddSelectedUsersToEventAsync() + { + try + { + var selectedUserIds = UserPickerModalRef + .SelectAllUsers + .Where(x => x.Value) + .Select(x => x.Key) + .ToList(); + + var removedAttendees = SelectedUserIds.Except(selectedUserIds).ToList(); + foreach (var attendeeId in removedAttendees) + { + await EventRegistrationAppService.UnRegisterAttendeeAsync(EventId, attendeeId); + } + + await EventRegistrationAppService.RegisterUsersAsync(EventId, selectedUserIds); + await UserPickerModalRef.CloseUserPickerModalAsync(); + + await GetAttendeesAsync(); + await InvokeAsync(StateHasChanged); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + private async Task OpenUserPickerModal() + { + await UserPickerModalRef.OpenUserPickerModalAsync(); + } + } +} diff --git a/src/EventHub.Admin.Web/Pages/EventManagement.razor b/src/EventHub.Admin.Web/Pages/EventManagement.razor new file mode 100644 index 0000000..1ce8d0e --- /dev/null +++ b/src/EventHub.Admin.Web/Pages/EventManagement.razor @@ -0,0 +1,295 @@ +@page "/events" +@using Microsoft.AspNetCore.Authorization +@using EventHub.Admin.Permissions +@using EventHub.Admin.Events +@using EventHub.Events +@using EventHub.Web +@using Microsoft.Extensions.Options +@using Volo.Abp.AspNetCore.Components.Notifications +@inherits EventHubComponentBase +@attribute [Authorize(EventHubPermissions.Events.Default)] +@inject IEventAppService EventAppService +@inject NavigationManager NavigationManager +@inject IOptions UrlOptions + + + + @L["Events"] + + + + + + + +