From 0fae22d395d77ec098472aa0002c6fc054719339 Mon Sep 17 00:00:00 2001 From: julian-mcnichols Date: Tue, 28 Jun 2022 16:06:56 -0500 Subject: [PATCH] Create Mock Billing Module * Create template module without auth that is invoked by new tenant logic to simulate subscription process --- AzureSaaSDevKit.sln | 6 + src/Saas.Admin/Saas.Admin.Service/Program.cs | 31 +- .../Services/Clients/BillingServiceClient.cs | 315 ++++++++++++++++++ .../Services/TenantService.cs | 7 +- .../Saas.Admin.Service/appsettings.json | 3 + .../billing-service-client-generator.nswag | 100 ++++++ .../Saas.Application.Web.csproj | 2 +- .../Saas.Billing.Service/AppConstants.cs | 27 ++ .../AppSettings/AppSettings.cs | 6 + .../AppSettings/AzureADB2COptions.cs | 9 + .../DTOs/NewSubscriptionRequest.cs | 8 + .../Controllers/DTOs/SubscriptionDTO.cs | 11 + .../Controllers/DTOs/UserDTO.cs | 14 + .../Controllers/SubscriptionController.cs | 28 ++ .../ICertificateValidationService.cs | 8 + .../Interfaces/ISubscriptionService.cs | 9 + .../Saas.Billing.Service/Program.cs | 45 +++ .../Properties/launchSettings.json | 31 ++ src/Saas.Billing/Saas.Billing.Service/SR.cs | 6 + .../Saas.Billing.Service.csproj | 23 ++ .../Services/CertificateValidationService.cs | 28 ++ .../Services/SubscriptionService.cs | 30 ++ .../CustomPrefixKeyVaultSecretManager.cs | 20 ++ .../appsettings.Development.json | 8 + .../Saas.Billing.Service/appsettings.json | 8 + .../billing-service-client-generator.nswag | 100 ++++++ .../Saas.Billing.Service/globalusings.cs | 7 + 27 files changed, 886 insertions(+), 4 deletions(-) create mode 100644 src/Saas.Admin/Saas.Admin.Service/Services/Clients/BillingServiceClient.cs create mode 100644 src/Saas.Admin/Saas.Admin.Service/billing-service-client-generator.nswag create mode 100644 src/Saas.Billing/Saas.Billing.Service/AppConstants.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/AppSettings/AppSettings.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/AppSettings/AzureADB2COptions.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/NewSubscriptionRequest.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/SubscriptionDTO.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/UserDTO.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Controllers/SubscriptionController.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Interfaces/ICertificateValidationService.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Interfaces/ISubscriptionService.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Program.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Properties/launchSettings.json create mode 100644 src/Saas.Billing/Saas.Billing.Service/SR.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Saas.Billing.Service.csproj create mode 100644 src/Saas.Billing/Saas.Billing.Service/Services/CertificateValidationService.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Services/SubscriptionService.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/Utilities/CustomPrefixKeyVaultSecretManager.cs create mode 100644 src/Saas.Billing/Saas.Billing.Service/appsettings.Development.json create mode 100644 src/Saas.Billing/Saas.Billing.Service/appsettings.json create mode 100644 src/Saas.Billing/Saas.Billing.Service/billing-service-client-generator.nswag create mode 100644 src/Saas.Billing/Saas.Billing.Service/globalusings.cs diff --git a/AzureSaaSDevKit.sln b/AzureSaaSDevKit.sln index a23d15c2..ea53fabe 100644 --- a/AzureSaaSDevKit.sln +++ b/AzureSaaSDevKit.sln @@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Permissions.Service", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.AspNetCore.Authorization.Tests", "src\Saas.Authorization\Saas.AspNetCore.Authorization.Tests\Saas.AspNetCore.Authorization.Tests.csproj", "{AEDE788C-35EF-4C03-AA2F-1D1D787001FA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Billing.Service", "src\Saas.Billing\Saas.Billing.Service\Saas.Billing.Service.csproj", "{48BA6628-649B-4845-8B8A-B2FFA4B10530}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,6 +70,10 @@ Global {AEDE788C-35EF-4C03-AA2F-1D1D787001FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEDE788C-35EF-4C03-AA2F-1D1D787001FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {AEDE788C-35EF-4C03-AA2F-1D1D787001FA}.Release|Any CPU.Build.0 = Release|Any CPU + {48BA6628-649B-4845-8B8A-B2FFA4B10530}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48BA6628-649B-4845-8B8A-B2FFA4B10530}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48BA6628-649B-4845-8B8A-B2FFA4B10530}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48BA6628-649B-4845-8B8A-B2FFA4B10530}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Saas.Admin/Saas.Admin.Service/Program.cs b/src/Saas.Admin/Saas.Admin.Service/Program.cs index be8d0f9e..e11b2249 100644 --- a/src/Saas.Admin/Saas.Admin.Service/Program.cs +++ b/src/Saas.Admin/Saas.Admin.Service/Program.cs @@ -11,6 +11,7 @@ using Saas.AspNetCore.Authorization.ClaimTransformers; var builder = WebApplication.CreateBuilder(args); X509Certificate2 permissionsApiCertificate; +X509Certificate2 paymentApiCertificate; if (builder.Environment.IsProduction()) { @@ -23,12 +24,17 @@ if (builder.Environment.IsProduction()) // Get certificate from secret imported above and parse it into an X509Certificate permissionsApiCertificate = new X509Certificate2(Convert.FromBase64String(builder.Configuration["KeyVault:PermissionsApiCert"]), builder.Configuration["KeyVault:PermissionsApiCertPassphrase"]); + + paymentApiCertificate = new X509Certificate2(Convert.FromBase64String(builder.Configuration["KeyVault:PaymentApiCert"]), builder.Configuration["KeyVault:PaymentApiCertPassphrase"]); } else { // If running locally, you must first set the certificate as a base 64 encoded string in your .NET secrets manager. - var certString = builder.Configuration["PermissionsApi:LocalCertificate"]; - permissionsApiCertificate = new X509Certificate2(Convert.FromBase64String(certString)); + var permissionCertString = builder.Configuration["PermissionsApi:LocalCertificate"]; + permissionsApiCertificate = new X509Certificate2(Convert.FromBase64String(permissionCertString)); + + var paymentCertString = builder.Configuration["PaymentApi:LocalCertificate"]; + paymentApiCertificate = new X509Certificate2(Convert.FromBase64String(paymentCertString)); } builder.Services.AddDbContext(options => @@ -128,6 +134,27 @@ builder.Services.AddHttpClient() + // Configure outgoing HTTP requests to include certificate for payment API + .ConfigurePrimaryHttpMessageHandler(() => + { + HttpClientHandler handler = new HttpClientHandler(); + handler.ClientCertificates.Add(paymentApiCertificate); + return handler; + }) + .ConfigureHttpClient(options => + { + options.BaseAddress = new Uri(builder.Configuration["PaymentApi:BaseUrl"]); + + if (builder.Environment.IsDevelopment()) + { + // The payment API expects the certificate to be provided to the application layer by the web server after the TLS handshake + // Since this doesn't happen locally, we need to do it ourselves + + options.DefaultRequestHeaders.Add("X-ARR-ClientCert", Convert.ToBase64String(paymentApiCertificate.GetRawCertData())); + } + }); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => diff --git a/src/Saas.Admin/Saas.Admin.Service/Services/Clients/BillingServiceClient.cs b/src/Saas.Admin/Saas.Admin.Service/Services/Clients/BillingServiceClient.cs new file mode 100644 index 00000000..2732387c --- /dev/null +++ b/src/Saas.Admin/Saas.Admin.Service/Services/Clients/BillingServiceClient.cs @@ -0,0 +1,315 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.15.10.0 (NJsonSchema v10.6.10.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// +// +// Manual Modifications: +// * Removed the following classes because PermissionsServiceClient already implements them: +// ApiException, ApiException, ProblemDetails +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" +#pragma warning disable 8603 // Disable "CS8603 Possible null reference return" + +namespace Saas.Admin.Service.Services +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.15.10.0 (NJsonSchema v10.6.10.0 (Newtonsoft.Json v13.0.0.0))")] + public partial interface IBillingServiceClient + { + /// Success + /// A server side error occurred. + System.Threading.Tasks.Task SubscriptionAsync(NewSubscriptionRequest body); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Success + /// A server side error occurred. + System.Threading.Tasks.Task SubscriptionAsync(NewSubscriptionRequest body, System.Threading.CancellationToken cancellationToken); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.15.10.0 (NJsonSchema v10.6.10.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class BillingServiceClient : IBillingServiceClient + { + private System.Net.Http.HttpClient _httpClient; + private System.Lazy _settings; + + public BillingServiceClient(System.Net.Http.HttpClient httpClient) + { + _httpClient = httpClient; + _settings = new System.Lazy(CreateSerializerSettings); + } + + private System.Text.Json.JsonSerializerOptions CreateSerializerSettings() + { + var settings = new System.Text.Json.JsonSerializerOptions(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + protected System.Text.Json.JsonSerializerOptions JsonSerializerSettings { get { return _settings.Value; } } + + partial void UpdateJsonSerializerSettings(System.Text.Json.JsonSerializerOptions settings); + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task SubscriptionAsync(NewSubscriptionRequest body) + { + return SubscriptionAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task SubscriptionAsync(NewSubscriptionRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("api/Subscription"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(System.Text.Json.JsonSerializer.Serialize(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 201) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 500) + { + string responseText_ = (response_.Content == null) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("Server Error", status_, responseText_, headers_, null); + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var typedBody = System.Text.Json.JsonSerializer.Deserialize(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (System.Text.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + var typedBody = await System.Text.Json.JsonSerializer.DeserializeAsync(responseStream, JsonSerializerSettings, cancellationToken).ConfigureAwait(false); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (System.Text.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[])value); + } + else if (value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array)value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.15.10.0 (NJsonSchema v10.6.10.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class NewSubscriptionRequest + { + + [System.Text.Json.Serialization.JsonPropertyName("customerId")] + public string CustomerId { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("productTierId")] + public string ProductTierId { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("serviceStartDate")] + public System.DateTimeOffset ServiceStartDate { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.15.10.0 (NJsonSchema v10.6.10.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class SubscriptionDTO + { + + [System.Text.Json.Serialization.JsonPropertyName("subscriptionId")] + public string SubscriptionId { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("customerId")] + public string CustomerId { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("productTierId")] + public string ProductTierId { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("createdDate")] + public System.DateTimeOffset CreatedDate { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("serviceStartDate")] + public System.DateTimeOffset ServiceStartDate { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("serviceEndDate")] + public System.DateTimeOffset ServiceEndDate { get; set; } + + } +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 +#pragma warning restore 3016 +#pragma warning restore 8603 \ No newline at end of file diff --git a/src/Saas.Admin/Saas.Admin.Service/Services/TenantService.cs b/src/Saas.Admin/Saas.Admin.Service/Services/TenantService.cs index d07aa9f5..c07d2591 100644 --- a/src/Saas.Admin/Saas.Admin.Service/Services/TenantService.cs +++ b/src/Saas.Admin/Saas.Admin.Service/Services/TenantService.cs @@ -7,12 +7,14 @@ public class TenantService : ITenantService { private readonly TenantsContext _context; private readonly IPermissionService _permissionService; + private readonly IBillingServiceClient _billingService; private readonly ILogger _logger; - public TenantService(TenantsContext tenantContext, IPermissionService permissionService, ILogger logger) + public TenantService(TenantsContext tenantContext, IPermissionService permissionService, IBillingServiceClient billingService, ILogger logger) { _context = tenantContext; _permissionService = permissionService; + _billingService = billingService; _logger = logger; } @@ -52,6 +54,9 @@ public class TenantService : ITenantService try { await _permissionService.AddUserPermissionsToTenantAsync(tenant.Id.ToString(), adminId, AppConstants.Roles.TenantAdmin); + + //TODO (SaaS): Implement the billing module to setup the appropriate tenant subscription + await _billingService.SubscriptionAsync(new NewSubscriptionRequest() { CustomerId = newTenantRequest.CreatorEmail, ProductTierId = newTenantRequest.ProductTierId.ToString(), ServiceStartDate = new DateTime()}); } catch (Exception ex) { diff --git a/src/Saas.Admin/Saas.Admin.Service/appsettings.json b/src/Saas.Admin/Saas.Admin.Service/appsettings.json index 9c09379e..aea40864 100644 --- a/src/Saas.Admin/Saas.Admin.Service/appsettings.json +++ b/src/Saas.Admin/Saas.Admin.Service/appsettings.json @@ -17,6 +17,9 @@ "PermissionsApi": { "BaseUrl": "https://localhost:7023" }, + "PaymentApi": { + "BaseUrl": "https://localhost:7210" + }, "ClaimToRoleTransformer": { "SourceClaimType": "permissions", //Name of the claim custom roles are in "RoleClaimtype": "MyCustomRoles", //Type of the claim to use in the new Identity (works along side of built in) diff --git a/src/Saas.Admin/Saas.Admin.Service/billing-service-client-generator.nswag b/src/Saas.Admin/Saas.Admin.Service/billing-service-client-generator.nswag new file mode 100644 index 00000000..054d9c37 --- /dev/null +++ b/src/Saas.Admin/Saas.Admin.Service/billing-service-client-generator.nswag @@ -0,0 +1,100 @@ +{ + "runtime": "Net60", + "defaultVariables": null, + "documentGenerator": { + "fromDocument": { + "json": "{\r\n \"openapi\": \"3.0.1\",\r\n \"info\": {\r\n \"title\": \"Saas.Billing.Service\",\r\n \"version\": \"1.0\"\r\n },\r\n \"paths\": {\r\n \"/api/Subscription\": {\r\n \"post\": {\r\n \"tags\": [\r\n \"Subscription\"\r\n ],\r\n \"requestBody\": {\r\n \"content\": {\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/NewSubscriptionRequest\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/NewSubscriptionRequest\"\r\n }\r\n },\r\n \"application/*+json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/NewSubscriptionRequest\"\r\n }\r\n }\r\n }\r\n },\r\n \"responses\": {\r\n \"201\": {\r\n \"description\": \"Success\",\r\n \"content\": {\r\n \"text/plain\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/SubscriptionDTO\"\r\n }\r\n },\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/SubscriptionDTO\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/SubscriptionDTO\"\r\n }\r\n }\r\n }\r\n },\r\n \"500\": {\r\n \"description\": \"Server Error\"\r\n },\r\n \"400\": {\r\n \"description\": \"Bad Request\",\r\n \"content\": {\r\n \"text/plain\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n },\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n },\r\n \"components\": {\r\n \"schemas\": {\r\n \"NewSubscriptionRequest\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"customerId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"productTierId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"serviceStartDate\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n }\r\n },\r\n \"additionalProperties\": false\r\n },\r\n \"ProblemDetails\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"type\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"title\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"status\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\",\r\n \"nullable\": true\r\n },\r\n \"detail\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"instance\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n }\r\n },\r\n \"additionalProperties\": {}\r\n },\r\n \"SubscriptionDTO\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"subscriptionId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"customerId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"productTierId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"createdDate\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n },\r\n \"serviceStartDate\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n },\r\n \"serviceEndDate\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n }\r\n },\r\n \"additionalProperties\": false\r\n }\r\n }\r\n }\r\n}", + "url": "https://localhost:7210/swagger/v1/swagger.json", + "output": null, + "newLineBehavior": "Auto" + } + }, + "codeGenerators": { + "openApiToCSharpClient": { + "clientBaseClass": "", + "configurationClass": "", + "generateClientClasses": true, + "generateClientInterfaces": true, + "clientBaseInterface": "", + "injectHttpClient": true, + "disposeHttpClient": true, + "protectedMethods": [], + "generateExceptionClasses": true, + "exceptionClass": "ApiException", + "wrapDtoExceptions": true, + "useHttpClientCreationMethod": false, + "httpClientType": "System.Net.Http.HttpClient", + "useHttpRequestMessageCreationMethod": false, + "useBaseUrl": false, + "generateBaseUrlProperty": true, + "generateSyncMethods": false, + "generatePrepareRequestAndProcessResponseAsAsyncMethods": false, + "exposeJsonSerializerSettings": false, + "clientClassAccessModifier": "public", + "typeAccessModifier": "public", + "generateContractsOutput": false, + "contractsNamespace": null, + "contractsOutputFilePath": null, + "parameterDateTimeFormat": "s", + "parameterDateFormat": "yyyy-MM-dd", + "generateUpdateJsonSerializerSettingsMethod": true, + "useRequestAndResponseSerializationSettings": false, + "serializeTypeInformation": false, + "queryNullValue": "", + "className": "BillingServiceClient", + "operationGenerationMode": "MultipleClientsFromOperationId", + "additionalNamespaceUsages": [], + "additionalContractNamespaceUsages": [], + "generateOptionalParameters": false, + "generateJsonMethods": false, + "enforceFlagEnums": false, + "parameterArrayType": "System.Collections.Generic.IEnumerable", + "parameterDictionaryType": "System.Collections.Generic.IDictionary", + "responseArrayType": "System.Collections.Generic.ICollection", + "responseDictionaryType": "System.Collections.Generic.IDictionary", + "wrapResponses": false, + "wrapResponseMethods": [], + "generateResponseClasses": true, + "responseClass": "SwaggerResponse", + "namespace": "Saas.Admin.Service.Services", + "requiredPropertiesMustBeDefined": true, + "dateType": "System.DateTimeOffset", + "jsonConverters": null, + "anyType": "object", + "dateTimeType": "System.DateTimeOffset", + "timeType": "System.TimeSpan", + "timeSpanType": "System.TimeSpan", + "arrayType": "System.Collections.Generic.ICollection", + "arrayInstanceType": "System.Collections.ObjectModel.Collection", + "dictionaryType": "System.Collections.Generic.IDictionary", + "dictionaryInstanceType": "System.Collections.Generic.Dictionary", + "arrayBaseType": "System.Collections.ObjectModel.Collection", + "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "classStyle": "Poco", + "jsonLibrary": "SystemTextJson", + "generateDefaultValues": true, + "generateDataAnnotations": true, + "excludedTypeNames": [], + "excludedParameterNames": [], + "handleReferences": false, + "generateImmutableArrayProperties": false, + "generateImmutableDictionaryProperties": false, + "jsonSerializerSettingsTransformationMethod": null, + "inlineNamedArrays": false, + "inlineNamedDictionaries": false, + "inlineNamedTuples": true, + "inlineNamedAny": false, + "generateDtoTypes": true, + "generateOptionalPropertiesAsNullable": false, + "generateNullableReferenceTypes": false, + "templateDirectory": null, + "typeNameGeneratorType": null, + "propertyNameGeneratorType": null, + "enumNameGeneratorType": null, + "serviceHost": null, + "serviceSchemes": null, + "output": "", + "newLineBehavior": "Auto" + } + } +} \ No newline at end of file diff --git a/src/Saas.Application/Saas.Application.Web/Saas.Application.Web.csproj b/src/Saas.Application/Saas.Application.Web/Saas.Application.Web.csproj index 7d90929d..9a4740ef 100644 --- a/src/Saas.Application/Saas.Application.Web/Saas.Application.Web.csproj +++ b/src/Saas.Application/Saas.Application.Web/Saas.Application.Web.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Saas.Billing/Saas.Billing.Service/AppConstants.cs b/src/Saas.Billing/Saas.Billing.Service/AppConstants.cs new file mode 100644 index 00000000..de0c7b9a --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/AppConstants.cs @@ -0,0 +1,27 @@ +namespace Saas.Billing.Service; + +public static class AppConstants +{ + public static class Policies + { + public const string Authenticated = "Authenticated"; + public const string GlobalAdmin = "Global_Admin"; + public const string TenantGlobalRead = "Tenant_Global_Read"; + public const string TenantRead = "Tenant_Read"; + public const string CreateTenant = "Create_Tenant"; + } + + public static class Roles + { + public const string GlobalAdmin = "GlobalAdmin"; + public const string TenantUser = "TenantUser"; + public const string TenantAdmin = "TenantAdmin"; + public const string Self = "Self"; + } + + public static class Scopes + { + public const string GlobalRead = "tenant.global.read"; + public const string Read = "tenant.read"; + } +} diff --git a/src/Saas.Billing/Saas.Billing.Service/AppSettings/AppSettings.cs b/src/Saas.Billing/Saas.Billing.Service/AppSettings/AppSettings.cs new file mode 100644 index 00000000..3c6856be --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/AppSettings/AppSettings.cs @@ -0,0 +1,6 @@ +namespace Saas.Billing.Service.Models.AppSettings; + +public class AppSettings +{ + public string SSLCertThumbprint { get; set; } = string.Empty; +} diff --git a/src/Saas.Billing/Saas.Billing.Service/AppSettings/AzureADB2COptions.cs b/src/Saas.Billing/Saas.Billing.Service/AppSettings/AzureADB2COptions.cs new file mode 100644 index 00000000..91d72f44 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/AppSettings/AzureADB2COptions.cs @@ -0,0 +1,9 @@ +namespace Saas.Billing.Service.Models.AppSettings; + +public class AzureADB2COptions +{ + public string ClientId { get; set; } = string.Empty; + public string ClientSecret { get; set; } = string.Empty; + public string TenantId { get; set; } = string.Empty; + public string Domain { get; set; } = string.Empty; +} diff --git a/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/NewSubscriptionRequest.cs b/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/NewSubscriptionRequest.cs new file mode 100644 index 00000000..54de8374 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/NewSubscriptionRequest.cs @@ -0,0 +1,8 @@ +namespace Saas.Billing.Service.Controllers.DTOs; + +public class NewSubscriptionRequest +{ + public string CustomerId { get; set; } = string.Empty; + public string ProductTierId { get; set; } = string.Empty; + public DateTime ServiceStartDate { get; set; } +} diff --git a/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/SubscriptionDTO.cs b/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/SubscriptionDTO.cs new file mode 100644 index 00000000..f0adc43d --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/SubscriptionDTO.cs @@ -0,0 +1,11 @@ +namespace Saas.Billing.Service.Controllers; + +public partial class SubscriptionDTO +{ + public string SubscriptionId { get; set; } = string.Empty; + public string CustomerId { get; set; } = string.Empty; + public string ProductTierId { get; set; } = string.Empty; + public System.DateTimeOffset CreatedDate { get; set; } + public System.DateTimeOffset ServiceStartDate { get; set; } + public System.DateTimeOffset ServiceEndDate { get; set; } +} diff --git a/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/UserDTO.cs b/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/UserDTO.cs new file mode 100644 index 00000000..1f21f34a --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Controllers/DTOs/UserDTO.cs @@ -0,0 +1,14 @@ +namespace Saas.Billing.Service.Controllers.DTOs; + + +public class UserDTO +{ + public UserDTO(string userId, string displayName) + { + UserId = userId; + DisplayName = displayName; + } + public string UserId { get; set; } + + public string DisplayName { get; set; } +} \ No newline at end of file diff --git a/src/Saas.Billing/Saas.Billing.Service/Controllers/SubscriptionController.cs b/src/Saas.Billing/Saas.Billing.Service/Controllers/SubscriptionController.cs new file mode 100644 index 00000000..0b918663 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Controllers/SubscriptionController.cs @@ -0,0 +1,28 @@ +using Saas.Billing.Service.Controllers.DTOs; +using Saas.Billing.Service.Interfaces; + +namespace Saas.Billing.Service.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class SubscriptionController : ControllerBase +{ + private readonly ILogger _logger; + private readonly ISubscriptionService _subscriptionService; + + public SubscriptionController(ISubscriptionService subscriptionService, ILogger logger) + { + _logger = logger; + _subscriptionService = subscriptionService; + } + + [HttpPost()] + [ProducesResponseType(typeof(SubscriptionDTO), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status200OK)] + public SubscriptionDTO PostSubscription(NewSubscriptionRequest request) + { + return _subscriptionService.AddSubscriptionAsync(request); + } +} \ No newline at end of file diff --git a/src/Saas.Billing/Saas.Billing.Service/Interfaces/ICertificateValidationService.cs b/src/Saas.Billing/Saas.Billing.Service/Interfaces/ICertificateValidationService.cs new file mode 100644 index 00000000..1af61e0a --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Interfaces/ICertificateValidationService.cs @@ -0,0 +1,8 @@ +using System.Security.Cryptography.X509Certificates; + +namespace Saas.Billing.Service.Interfaces; + +public interface ICertificateValidationService +{ + public bool ValidateCertificate(X509Certificate2 clientCertificate); +} diff --git a/src/Saas.Billing/Saas.Billing.Service/Interfaces/ISubscriptionService.cs b/src/Saas.Billing/Saas.Billing.Service/Interfaces/ISubscriptionService.cs new file mode 100644 index 00000000..59b5fddd --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Interfaces/ISubscriptionService.cs @@ -0,0 +1,9 @@ +using Saas.Billing.Service.Controllers; +using Saas.Billing.Service.Controllers.DTOs; + +namespace Saas.Billing.Service.Interfaces; + +public interface ISubscriptionService +{ + SubscriptionDTO AddSubscriptionAsync(NewSubscriptionRequest newTenantRequest); +} \ No newline at end of file diff --git a/src/Saas.Billing/Saas.Billing.Service/Program.cs b/src/Saas.Billing/Saas.Billing.Service/Program.cs new file mode 100644 index 00000000..86d5b5ff --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Program.cs @@ -0,0 +1,45 @@ +using Azure.Identity; +using Saas.Billing.Service; +using Saas.Billing.Service.Interfaces; +using Saas.Billing.Service.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +if (builder.Environment.IsProduction()) +{ + // Get Secrets From Azure Key Vault if in production. If not in production, secrets are automatically loaded in from the .NET secrets manager + // https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0 + + // We don't want to fetch all the secrets for the other microservices in the app/solution, so we only fetch the ones with the prefix of "signupadmin-". + // https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0#use-a-key-name-prefix + + builder.Configuration.AddAzureKeyVault( + new Uri(builder.Configuration[SR.KeyVaultProperty]), + new DefaultAzureCredential(), + new CustomPrefixKeyVaultSecretManager("billing")); +} + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddScoped(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/src/Saas.Billing/Saas.Billing.Service/Properties/launchSettings.json b/src/Saas.Billing/Saas.Billing.Service/Properties/launchSettings.json new file mode 100644 index 00000000..74a91f31 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:46333", + "sslPort": 44369 + } + }, + "profiles": { + "Saas.Payment.Service": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7210;http://localhost:5210", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Saas.Billing/Saas.Billing.Service/SR.cs b/src/Saas.Billing/Saas.Billing.Service/SR.cs new file mode 100644 index 00000000..e53737e8 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/SR.cs @@ -0,0 +1,6 @@ +namespace Saas.Billing.Service; + +public static class SR +{ + public const string KeyVaultProperty = "KeyVault:Url"; +} diff --git a/src/Saas.Billing/Saas.Billing.Service/Saas.Billing.Service.csproj b/src/Saas.Billing/Saas.Billing.Service/Saas.Billing.Service.csproj new file mode 100644 index 00000000..d3141904 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Saas.Billing.Service.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + 6de048a5-295d-4b2f-a1bb-a00ba36bc91d + + + + + + + + + + + + + + + + diff --git a/src/Saas.Billing/Saas.Billing.Service/Services/CertificateValidationService.cs b/src/Saas.Billing/Saas.Billing.Service/Services/CertificateValidationService.cs new file mode 100644 index 00000000..4622f1f0 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Services/CertificateValidationService.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Saas.Billing.Service.Interfaces; +using Saas.Billing.Service.Models.AppSettings; +using System.Security.Cryptography.X509Certificates; +namespace Saas.Billing.Service.Services; + +public class CertificateValidationService : ICertificateValidationService +{ + private readonly AppSettings _appSettings; + private readonly ILogger _logger; + public CertificateValidationService(IOptions appSettings, ILogger logger) + { + _appSettings = appSettings.Value; + _logger = logger; + } + public bool ValidateCertificate(X509Certificate2 clientCertificate) + { + // Insert any other custom certificate validation logic here + //_logger should be used along with this logic. + + // Do not check your certificate thumbprint into your git repository. + // Another option would be to load in your certificate thumbprint from azure keyvault. + var expectedCertificateThumbPrint = _appSettings.SSLCertThumbprint; + + return clientCertificate.Thumbprint == expectedCertificateThumbPrint; + } +} diff --git a/src/Saas.Billing/Saas.Billing.Service/Services/SubscriptionService.cs b/src/Saas.Billing/Saas.Billing.Service/Services/SubscriptionService.cs new file mode 100644 index 00000000..dc9f88ca --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Services/SubscriptionService.cs @@ -0,0 +1,30 @@ +using Saas.Billing.Service.Controllers; +using Saas.Billing.Service.Controllers.DTOs; +using Saas.Billing.Service.Interfaces; + +namespace Saas.Billing.Service.Services; + +public class SubscriptionService : ISubscriptionService +{ + private readonly ILogger _logger; + + public SubscriptionService(ILogger logger) + { + _logger = logger; + } + + //TODO (SaaS): Implement your payment processor or integrate with another service + public SubscriptionDTO AddSubscriptionAsync(NewSubscriptionRequest newTenantRequest) + { + SubscriptionDTO returnValue = new SubscriptionDTO() + { + SubscriptionId = new Guid().ToString(), + CustomerId = newTenantRequest.CustomerId, + ProductTierId = newTenantRequest.ProductTierId, + CreatedDate = DateTime.Now, + ServiceStartDate = DateTime.Now, + }; + return returnValue; + } + +} diff --git a/src/Saas.Billing/Saas.Billing.Service/Utilities/CustomPrefixKeyVaultSecretManager.cs b/src/Saas.Billing/Saas.Billing.Service/Utilities/CustomPrefixKeyVaultSecretManager.cs new file mode 100644 index 00000000..7273b9a6 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/Utilities/CustomPrefixKeyVaultSecretManager.cs @@ -0,0 +1,20 @@ +using Azure.Extensions.AspNetCore.Configuration.Secrets; +using Azure.Security.KeyVault.Secrets; + +namespace Saas.Billing.Service; + +// This is to use key name prefixes to only load in the secrets that pertain to this microservice +// https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0#use-a-key-name-prefix +public class CustomPrefixKeyVaultSecretManager : KeyVaultSecretManager +{ + private readonly string _prefix; + + public CustomPrefixKeyVaultSecretManager(string prefix) + => _prefix = $"{prefix}-"; + + public override bool Load(SecretProperties properties) + => properties.Name.StartsWith(_prefix); + + public override string GetKey(KeyVaultSecret secret) + => secret.Name[_prefix.Length..].Replace("--", ConfigurationPath.KeyDelimiter); +} diff --git a/src/Saas.Billing/Saas.Billing.Service/appsettings.Development.json b/src/Saas.Billing/Saas.Billing.Service/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Saas.Billing/Saas.Billing.Service/appsettings.json b/src/Saas.Billing/Saas.Billing.Service/appsettings.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Saas.Billing/Saas.Billing.Service/billing-service-client-generator.nswag b/src/Saas.Billing/Saas.Billing.Service/billing-service-client-generator.nswag new file mode 100644 index 00000000..03ef5e31 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/billing-service-client-generator.nswag @@ -0,0 +1,100 @@ +{ + "runtime": "Net60", + "defaultVariables": null, + "documentGenerator": { + "fromDocument": { + "json": "{\r\n \"openapi\": \"3.0.1\",\r\n \"info\": {\r\n \"title\": \"Saas.Billing.Service\",\r\n \"version\": \"1.0\"\r\n },\r\n \"paths\": {\r\n \"/Subscription\": {\r\n \"post\": {\r\n \"tags\": [\r\n \"Subscription\"\r\n ],\r\n \"requestBody\": {\r\n \"content\": {\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/NewSubscriptionRequest\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/NewSubscriptionRequest\"\r\n }\r\n },\r\n \"application/*+json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/NewSubscriptionRequest\"\r\n }\r\n }\r\n }\r\n },\r\n \"responses\": {\r\n \"201\": {\r\n \"description\": \"Success\",\r\n \"content\": {\r\n \"text/plain\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/SubscriptionDTO\"\r\n }\r\n },\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/SubscriptionDTO\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/SubscriptionDTO\"\r\n }\r\n }\r\n }\r\n },\r\n \"401\": {\r\n \"description\": \"Unauthorized\",\r\n \"content\": {\r\n \"text/plain\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n },\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n }\r\n }\r\n },\r\n \"500\": {\r\n \"description\": \"Server Error\"\r\n },\r\n \"400\": {\r\n \"description\": \"Bad Request\",\r\n \"content\": {\r\n \"text/plain\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n },\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/ProblemDetails\"\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n },\r\n \"components\": {\r\n \"schemas\": {\r\n \"NewSubscriptionRequest\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"customerId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"productTierId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"serviceStartDate\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n }\r\n },\r\n \"additionalProperties\": false\r\n },\r\n \"ProblemDetails\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"type\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"title\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"status\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\",\r\n \"nullable\": true\r\n },\r\n \"detail\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"instance\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n }\r\n },\r\n \"additionalProperties\": {}\r\n },\r\n \"SubscriptionDTO\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"subscriptionId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"customerId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"productTierId\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"createdDate\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n },\r\n \"serviceStartDate\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n },\r\n \"serviceEndDate\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n }\r\n },\r\n \"additionalProperties\": false\r\n }\r\n }\r\n }\r\n}", + "url": "https://localhost:7210/swagger/v1/swagger.json", + "output": null, + "newLineBehavior": "Auto" + } + }, + "codeGenerators": { + "openApiToCSharpClient": { + "clientBaseClass": "", + "configurationClass": "", + "generateClientClasses": true, + "generateClientInterfaces": true, + "clientBaseInterface": "", + "injectHttpClient": true, + "disposeHttpClient": true, + "protectedMethods": [], + "generateExceptionClasses": true, + "exceptionClass": "ApiException", + "wrapDtoExceptions": true, + "useHttpClientCreationMethod": false, + "httpClientType": "System.Net.Http.HttpClient", + "useHttpRequestMessageCreationMethod": false, + "useBaseUrl": false, + "generateBaseUrlProperty": true, + "generateSyncMethods": false, + "generatePrepareRequestAndProcessResponseAsAsyncMethods": false, + "exposeJsonSerializerSettings": false, + "clientClassAccessModifier": "public", + "typeAccessModifier": "public", + "generateContractsOutput": false, + "contractsNamespace": null, + "contractsOutputFilePath": null, + "parameterDateTimeFormat": "s", + "parameterDateFormat": "yyyy-MM-dd", + "generateUpdateJsonSerializerSettingsMethod": true, + "useRequestAndResponseSerializationSettings": false, + "serializeTypeInformation": false, + "queryNullValue": "", + "className": "PaymentServiceClient", + "operationGenerationMode": "MultipleClientsFromOperationId", + "additionalNamespaceUsages": [], + "additionalContractNamespaceUsages": [], + "generateOptionalParameters": false, + "generateJsonMethods": false, + "enforceFlagEnums": false, + "parameterArrayType": "System.Collections.Generic.IEnumerable", + "parameterDictionaryType": "System.Collections.Generic.IDictionary", + "responseArrayType": "System.Collections.Generic.ICollection", + "responseDictionaryType": "System.Collections.Generic.IDictionary", + "wrapResponses": false, + "wrapResponseMethods": [], + "generateResponseClasses": true, + "responseClass": "SwaggerResponse", + "namespace": "Saas.Admin.Service.Services", + "requiredPropertiesMustBeDefined": true, + "dateType": "System.DateTimeOffset", + "jsonConverters": null, + "anyType": "object", + "dateTimeType": "System.DateTimeOffset", + "timeType": "System.TimeSpan", + "timeSpanType": "System.TimeSpan", + "arrayType": "System.Collections.Generic.ICollection", + "arrayInstanceType": "System.Collections.ObjectModel.Collection", + "dictionaryType": "System.Collections.Generic.IDictionary", + "dictionaryInstanceType": "System.Collections.Generic.Dictionary", + "arrayBaseType": "System.Collections.ObjectModel.Collection", + "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "classStyle": "Poco", + "jsonLibrary": "SystemTextJson", + "generateDefaultValues": true, + "generateDataAnnotations": true, + "excludedTypeNames": [], + "excludedParameterNames": [], + "handleReferences": false, + "generateImmutableArrayProperties": false, + "generateImmutableDictionaryProperties": false, + "jsonSerializerSettingsTransformationMethod": null, + "inlineNamedArrays": false, + "inlineNamedDictionaries": false, + "inlineNamedTuples": true, + "inlineNamedAny": false, + "generateDtoTypes": true, + "generateOptionalPropertiesAsNullable": false, + "generateNullableReferenceTypes": false, + "templateDirectory": null, + "typeNameGeneratorType": null, + "propertyNameGeneratorType": null, + "enumNameGeneratorType": null, + "serviceHost": null, + "serviceSchemes": null, + "output": "", + "newLineBehavior": "Auto" + } + } +} \ No newline at end of file diff --git a/src/Saas.Billing/Saas.Billing.Service/globalusings.cs b/src/Saas.Billing/Saas.Billing.Service/globalusings.cs new file mode 100644 index 00000000..21ee4444 --- /dev/null +++ b/src/Saas.Billing/Saas.Billing.Service/globalusings.cs @@ -0,0 +1,7 @@ +global using System.ComponentModel.DataAnnotations; +global using System.Reflection; +global using System.Runtime.Serialization; + +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.Identity.Web; \ No newline at end of file