diff --git a/src/EventHub.Application.Contracts/Organizations/FeatureOfPlanDefinitionDto.cs b/src/EventHub.Application.Contracts/Organizations/FeatureOfPlanDefinitionDto.cs new file mode 100644 index 0000000..d4e1e0d --- /dev/null +++ b/src/EventHub.Application.Contracts/Organizations/FeatureOfPlanDefinitionDto.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace EventHub.Organizations; + +public class FeatureOfPlanDefinitionDto +{ + public uint? MaxAllowedEventsCount { get; set; } + + public uint? MaxAllowedTracksCountInOneEvent { get; set; } + + public uint? MaxAllowedAttendeesCountInOneEvent { get; set; } + + public List AdditionalFeatureInfos { get; set; } + + public FeatureOfPlanDefinitionDto() + { + AdditionalFeatureInfos = new List(); + } +} diff --git a/src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs b/src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs index ce98bbb..8a0b4ec 100644 --- a/src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs +++ b/src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; @@ -21,5 +22,7 @@ namespace EventHub.Organizations Task UpdateAsync(Guid id, UpdateOrganizationDto input); Task GetProfilePictureAsync(Guid id); + + Task> GetPlanInfosAsync(); } } diff --git a/src/EventHub.Application.Contracts/Organizations/PlanInfoDefinitionDto.cs b/src/EventHub.Application.Contracts/Organizations/PlanInfoDefinitionDto.cs new file mode 100644 index 0000000..9c9de31 --- /dev/null +++ b/src/EventHub.Application.Contracts/Organizations/PlanInfoDefinitionDto.cs @@ -0,0 +1,25 @@ +namespace EventHub.Organizations; + +public class PlanInfoDefinitionDto +{ + public OrganizationPlanType PlanType { get; set; } + + public string Description { get; set; } + + public bool IsActive { get; set; } + + public decimal Price { get; set; } + + public bool IsExtendable { get; set; } + + public int? CanBeExtendedAfterHowManyMonths { get; set; } + + public int? OnePremiumPeriodAsMonth { get; set; } + + public FeatureOfPlanDefinitionDto Feature { get; set; } + + public PlanInfoDefinitionDto() + { + Feature = new FeatureOfPlanDefinitionDto(); + } +} diff --git a/src/EventHub.Application/EventHubApplicationAutoMapperProfile.cs b/src/EventHub.Application/EventHubApplicationAutoMapperProfile.cs index f16f205..b061662 100644 --- a/src/EventHub.Application/EventHubApplicationAutoMapperProfile.cs +++ b/src/EventHub.Application/EventHubApplicationAutoMapperProfile.cs @@ -5,6 +5,7 @@ using EventHub.Events.Registrations; using EventHub.Members; using EventHub.Organizations; using EventHub.Organizations.Memberships; +using EventHub.Organizations.PaymentRequests; using EventHub.Users; using Volo.Abp.AutoMapper; using Volo.Abp.Identity; @@ -48,6 +49,9 @@ namespace EventHub CreateMap(); CreateMap(); + + CreateMap(); + CreateMap(); } } } diff --git a/src/EventHub.Application/Organizations/OrganizationAppService.cs b/src/EventHub.Application/Organizations/OrganizationAppService.cs index 3205487..cf19673 100644 --- a/src/EventHub.Application/Organizations/OrganizationAppService.cs +++ b/src/EventHub.Application/Organizations/OrganizationAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using EventHub.Organizations.Memberships; +using EventHub.Organizations.PaymentRequests; using EventHub.Users; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; @@ -21,19 +22,22 @@ namespace EventHub.Organizations private readonly OrganizationManager _organizationManager; private readonly IBlobContainer _organizationBlobContainer; private readonly IUserRepository _userRepository; + private readonly IPlanInfoDefinitionStore _planInfoDefinitionStore; public OrganizationAppService( IRepository organizationRepository, IOrganizationMembershipRepository organizationMembershipsRepository, OrganizationManager organizationManager, IBlobContainer organizationBlobContainer, - IUserRepository userRepository) + IUserRepository userRepository, + IPlanInfoDefinitionStore planInfoDefinitionStore) { _organizationRepository = organizationRepository; _organizationMembershipsRepository = organizationMembershipsRepository; _organizationManager = organizationManager; _organizationBlobContainer = organizationBlobContainer; _userRepository = userRepository; + _planInfoDefinitionStore = planInfoDefinitionStore; } [Authorize] @@ -163,6 +167,13 @@ namespace EventHub.Organizations return new RemoteStreamContent(pictureContent, blobName); } + [Authorize] + public async Task> GetPlanInfosAsync() + { + var dd = await _planInfoDefinitionStore.GetPlanInfosAsync(); + return ObjectMapper.Map, List>(dd); + } + private async Task SaveProfilePictureAsync(Guid id, IRemoteStreamContent streamContent) { var blobName = id.ToString(); @@ -170,4 +181,4 @@ namespace EventHub.Organizations await _organizationBlobContainer.SaveAsync(blobName, streamContent.GetStream(), overrideExisting: true); } } -} \ No newline at end of file +} diff --git a/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs b/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs index 1097ad3..89dd99e 100644 --- a/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs +++ b/src/EventHub.Domain.Shared/Organizations/OrganizationContst.cs @@ -27,6 +27,8 @@ public const int MaxMediumUsernameLength = 24; + public const string ProductNamePrefix = "EventHub"; + public static string[] AllowedProfilePictureExtensions = { ".jpg", ".png" }; } } diff --git a/src/EventHub.Domain.Shared/Organizations/OrganizationPaymentRequestExtraParameterConfiguration.cs b/src/EventHub.Domain.Shared/Organizations/OrganizationPaymentRequestExtraParameterConfiguration.cs index 4954fc3..dccf4d0 100644 --- a/src/EventHub.Domain.Shared/Organizations/OrganizationPaymentRequestExtraParameterConfiguration.cs +++ b/src/EventHub.Domain.Shared/Organizations/OrganizationPaymentRequestExtraParameterConfiguration.cs @@ -6,6 +6,8 @@ namespace EventHub.Organizations public bool IsExtend { get; set; } - public int PremiumPeriodAsMonth { get; set; } + public int? PremiumPeriodAsMonth { get; set; } + + public OrganizationPlanType TargetPlanType { get; set; } } -} \ No newline at end of file +} diff --git a/src/EventHub.Domain.Shared/Organizations/OrganizationPlanType.cs b/src/EventHub.Domain.Shared/Organizations/OrganizationPlanType.cs index c48cdd7..1e21e68 100644 --- a/src/EventHub.Domain.Shared/Organizations/OrganizationPlanType.cs +++ b/src/EventHub.Domain.Shared/Organizations/OrganizationPlanType.cs @@ -2,6 +2,7 @@ namespace EventHub.Organizations { public enum OrganizationPlanType : byte { - Premium, + Free = 0, + Premium } -} \ No newline at end of file +} diff --git a/src/EventHub.Domain/Organizations/PaymentRequests/FeatureOfPlanDefinition.cs b/src/EventHub.Domain/Organizations/PaymentRequests/FeatureOfPlanDefinition.cs new file mode 100644 index 0000000..0c29a7f --- /dev/null +++ b/src/EventHub.Domain/Organizations/PaymentRequests/FeatureOfPlanDefinition.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; + +namespace EventHub.Organizations.PaymentRequests; + +public class FeatureOfPlanDefinition +{ + [CanBeNull] + [Range(0, uint.MaxValue, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + public uint? MaxAllowedEventsCount { get; set; } + + [CanBeNull] + [Range(0, uint.MaxValue, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + public uint? MaxAllowedTracksCountInOneEvent { get; set; } + + [CanBeNull] + [Range(0, uint.MaxValue, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + public uint? MaxAllowedAttendeesCountInOneEvent { get; set; } + + [NotNull] + public List AdditionalFeatureInfos { get; set; } +} diff --git a/src/EventHub.Domain/Organizations/PaymentRequests/IPlanInfoDefinitionStore.cs b/src/EventHub.Domain/Organizations/PaymentRequests/IPlanInfoDefinitionStore.cs new file mode 100644 index 0000000..a064388 --- /dev/null +++ b/src/EventHub.Domain/Organizations/PaymentRequests/IPlanInfoDefinitionStore.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace EventHub.Organizations.PaymentRequests; + +public interface IPlanInfoDefinitionStore +{ + Task> GetPlanInfosAsync(); +} diff --git a/src/EventHub.Domain/Organizations/PaymentRequests/PaymentRequestEventHandler.cs b/src/EventHub.Domain/Organizations/PaymentRequests/PaymentRequestEventHandler.cs index c972580..39fe0bc 100644 --- a/src/EventHub.Domain/Organizations/PaymentRequests/PaymentRequestEventHandler.cs +++ b/src/EventHub.Domain/Organizations/PaymentRequests/PaymentRequestEventHandler.cs @@ -135,14 +135,14 @@ namespace EventHub.Organizations.PaymentRequests if (organizationPaymentRequestExtraParameter.IsExtend) { organization.UpgradeToPremium( - organization.PremiumEndDate!.Value.AddMonths(organizationPaymentRequestExtraParameter.PremiumPeriodAsMonth)); + organization.PremiumEndDate!.Value.AddMonths(organizationPaymentRequestExtraParameter.PremiumPeriodAsMonth!.Value)); } else { - organization.UpgradeToPremium(DateTime.Now.AddMonths(organizationPaymentRequestExtraParameter.PremiumPeriodAsMonth)); + organization.UpgradeToPremium(DateTime.Now.AddMonths(organizationPaymentRequestExtraParameter.PremiumPeriodAsMonth!.Value)); } return await _organizationRepository.UpdateAsync(organization, true); } } -} \ No newline at end of file +} diff --git a/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoDefinition.cs b/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoDefinition.cs new file mode 100644 index 0000000..6fad99e --- /dev/null +++ b/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoDefinition.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; + +namespace EventHub.Organizations.PaymentRequests; + +public class PlanInfoDefinition +{ + public const string PlanInfo = "PlanInfos"; + + public OrganizationPlanType PlanType { get; set; } + + public string Description { get; set; } + + public bool IsActive { get; set; } + + [Range(1.0, 100.0, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + public decimal Price { get; set; } = 1.0M; + + public bool IsExtendable { get; set; } + + [CanBeNull] + [Range(0, 12, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + public int? CanBeExtendedAfterHowManyMonths { get; set; } + + [CanBeNull] + [Range(1, 24, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + public int? OnePremiumPeriodAsMonth { get; set; } = 12; + + public FeatureOfPlanDefinition Feature { get; set; } + + public PlanInfoDefinition() + { + Feature = new FeatureOfPlanDefinition(); + Feature.AdditionalFeatureInfos = new List(); + } +} diff --git a/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoDefinitionStore.cs b/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoDefinitionStore.cs new file mode 100644 index 0000000..118e4c3 --- /dev/null +++ b/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoDefinitionStore.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace EventHub.Organizations.PaymentRequests; + +public class PlanInfoDefinitionStore : IPlanInfoDefinitionStore, ITransientDependency +{ + protected PlanInfoOptions PlanInfoOptions { get; } + + public PlanInfoDefinitionStore(IOptions planInfoOptions) + { + PlanInfoOptions = planInfoOptions.Value; + } + + public Task> GetPlanInfosAsync() + { + return Task.FromResult(PlanInfoOptions.Infos); + } +} diff --git a/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoOptions.cs b/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoOptions.cs new file mode 100644 index 0000000..5e5d48d --- /dev/null +++ b/src/EventHub.Domain/Organizations/PaymentRequests/PlanInfoOptions.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace EventHub.Organizations.PaymentRequests; + +public class PlanInfoOptions +{ + public List Infos { get; } + + public PlanInfoOptions() + { + Infos = new List(); + } + + public PlanInfoOptions AddPlanInfos(List infos) + { + foreach (var info in infos) + { + AddPlanInfo(info); + } + + return this; + } + + private PlanInfoOptions AddPlanInfo(PlanInfoDefinition info) + { + Infos.AddIfNotContains(info); + + return this; + } +} diff --git a/src/EventHub.HttpApi.Host/EventHubHttpApiHostModule.cs b/src/EventHub.HttpApi.Host/EventHubHttpApiHostModule.cs index ce0a1ec..04478b1 100644 --- a/src/EventHub.HttpApi.Host/EventHubHttpApiHostModule.cs +++ b/src/EventHub.HttpApi.Host/EventHubHttpApiHostModule.cs @@ -2,9 +2,11 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using EventHub.EntityFrameworkCore; using EventHub.Events; using EventHub.Organizations; +using EventHub.Organizations.PaymentRequests; using EventHub.Utils; using EventHub.Web; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -15,6 +17,7 @@ using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using StackExchange.Redis; using Volo.Abp; @@ -63,6 +66,7 @@ namespace EventHub ConfigureBackgroundJobs(); ConfigureAutoApiControllers(); ConfigureTiming(); + ConfigurePremiumPlanInfo(context, configuration); } private void ConfigureAutoApiControllers() @@ -190,6 +194,47 @@ namespace EventHub { Configure(options => { options.Kind = DateTimeKind.Utc; }); } + + private void ConfigurePremiumPlanInfo(ServiceConfigurationContext context, IConfiguration configuration) + { + context.Services.AddOptions>() + .Bind(configuration.GetSection(PlanInfoDefinition.PlanInfo)) + .ValidateDataAnnotations() + .Validate(configOfDefinitions => + { + foreach (var config in configOfDefinitions) + { + var isValidPlanType = Enum.IsDefined(typeof(OrganizationPlanType), config.PlanType); + if (!isValidPlanType) + { + return false; + } + + var isExistSamePlan = configOfDefinitions.Count(x => + x.Price == config.Price && x.PlanType == config.PlanType); + if (isExistSamePlan > 1) + { + return false; + } + + if (config.IsActive && config.IsExtendable) + { + Check.NotNull(config.OnePremiumPeriodAsMonth, nameof(config.OnePremiumPeriodAsMonth)); + Check.NotNull(config.CanBeExtendedAfterHowManyMonths, nameof(config.CanBeExtendedAfterHowManyMonths)); + return config.OnePremiumPeriodAsMonth > config.CanBeExtendedAfterHowManyMonths; + } + } + + return true; + }, "PlanInfoDefinition is not valid!"); + + var planInfos = context.Services.GetRequiredServiceLazy>>(); + + Configure(options => + { + options.AddPlanInfos(planInfos.Value.Value); + }); + } public override void OnApplicationInitialization(ApplicationInitializationContext context) { diff --git a/src/EventHub.HttpApi.Host/appsettings.json b/src/EventHub.HttpApi.Host/appsettings.json index 22c8268..2360cb6 100644 --- a/src/EventHub.HttpApi.Host/appsettings.json +++ b/src/EventHub.HttpApi.Host/appsettings.json @@ -18,5 +18,34 @@ "Secret": "PAYPAL_SECRET", "Environment": "Sandbox" } - } + }, + "PlanInfos": [ + { + "PlanType": "Free", + "Description": "For individuals and small organizations.", + "IsActive": true, + "Price": 0, + "IsExtendable": false, + "Feature": { + "MaxAllowedEventsCount": 2, + "MaxAllowedTracksCountInOneEvent": 2, + "MaxAllowedAttendeesCountInOneEvent": 1000 + } + }, + { + "PlanType": "Premium", + "Description": "For those organize regular events.", + "IsActive": true, + "Price": 29.90, + "IsExtendable": true, + "CanBeExtendedAfterHowManyMonths": 9, + "OnePremiumPeriodAsMonth": 12, + "Feature": { + "AdditionalFeatureInfos": [ + "Be more prominent in the organization list.", + "Premium badge in the organization page." + ] + } + } + ] } diff --git a/src/EventHub.HttpApi/Controllers/Organizations/OrganizationController.cs b/src/EventHub.HttpApi/Controllers/Organizations/OrganizationController.cs index b8a60c3..6b61f88 100644 --- a/src/EventHub.HttpApi/Controllers/Organizations/OrganizationController.cs +++ b/src/EventHub.HttpApi/Controllers/Organizations/OrganizationController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using EventHub.Organizations; using Microsoft.AspNetCore.Mvc; @@ -84,5 +85,12 @@ namespace EventHub.Controllers.Organizations return remoteStreamContent; } + + [HttpGet] + [Route("plan-infos")] + public async Task> GetPlanInfosAsync() + { + return await _organizationAppService.GetPlanInfosAsync(); + } } } diff --git a/src/EventHub.Web/EventHubWebModule.cs b/src/EventHub.Web/EventHubWebModule.cs index 82edceb..89650e1 100644 --- a/src/EventHub.Web/EventHubWebModule.cs +++ b/src/EventHub.Web/EventHubWebModule.cs @@ -2,7 +2,6 @@ using System; using System.IO; using EventHub.Localization; using EventHub.Web.Menus; -using EventHub.Web.PaymentRequests; using EventHub.Web.Theme; using EventHub.Web.Theme.Bundling; using EventHub.Web.Utils; @@ -86,7 +85,6 @@ namespace EventHub.Web ConfigureCookies(context); ConfigureSwaggerServices(context.Services); ConfigureRazorPageOptions(); - ConfigurePremiumPlanInfo(context, configuration); } private void ConfigureBundles() @@ -233,22 +231,6 @@ namespace EventHub.Web .PersistKeysToStackExchangeRedis(redis, "EventHub-Protection-Keys"); } - private void ConfigurePremiumPlanInfo(ServiceConfigurationContext context, IConfiguration configuration) - { - context.Services.AddOptions() - .Bind(configuration.GetSection(PremiumPlanInfoOptions.OrganizationPlanInfo)) - .ValidateDataAnnotations() - .Validate(config => - { - if (config.IsActive) - { - return config.OnePremiumPeriodAsMonth > config.CanBeExtendedAfterHowManyMonths; - } - - return true; - }, "OnePremiumPeriodAsMonth must be greater than CanBeExtendedAfterHowManyMonths."); - } - public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); diff --git a/src/EventHub.Web/Pages/Organizations/Components/_upgradeOrExtendOrganizationSection.cshtml b/src/EventHub.Web/Pages/Organizations/Components/_upgradeOrExtendOrganizationSection.cshtml index e21c089..4823fe2 100644 --- a/src/EventHub.Web/Pages/Organizations/Components/_upgradeOrExtendOrganizationSection.cshtml +++ b/src/EventHub.Web/Pages/Organizations/Components/_upgradeOrExtendOrganizationSection.cshtml @@ -1,11 +1,14 @@ -@using EventHub.Web.PaymentRequests -@using Microsoft.Extensions.Options +@using EventHub.Organizations @model EventHub.Organizations.OrganizationProfileDto -@inject IOptionsSnapshot PremiumPlanInfoOptionsSnapshot +@inject IOrganizationAppService OrganizationAppService + +@{ + var premiumPlanInfo = (await OrganizationAppService.GetPlanInfosAsync()).MaxBy(x => x.Price)!; +} - @if (Model.IsPremium && PremiumPlanInfoOptionsSnapshot.Value.IsActive && PremiumPlanInfoOptionsSnapshot.Value.IsExtendable && Model.PremiumEndDate!.Value.IsBetween(DateTime.Now, DateTime.Now.AddMonths(PremiumPlanInfoOptionsSnapshot.Value.OnePremiumPeriodAsMonth - PremiumPlanInfoOptionsSnapshot.Value.CanBeExtendedAfterHowManyMonths))) + @if (Model.IsPremium && premiumPlanInfo.IsActive && premiumPlanInfo.IsExtendable && Model.PremiumEndDate!.Value.IsBetween(DateTime.Now, DateTime.Now.AddMonths(premiumPlanInfo.OnePremiumPeriodAsMonth!.Value - premiumPlanInfo.CanBeExtendedAfterHowManyMonths!.Value))) { Premium end date: @Model.PremiumEndDate!.Value.ToShortDateString() } @@ -13,11 +16,11 @@ Edit - @if (PremiumPlanInfoOptionsSnapshot.Value.IsActive) + @if (premiumPlanInfo.IsActive) { @if (Model.IsPremium) { - @if (PremiumPlanInfoOptionsSnapshot.Value.IsExtendable && Model.PremiumEndDate!.Value.IsBetween(DateTime.Now, DateTime.Now.AddMonths(PremiumPlanInfoOptionsSnapshot.Value.OnePremiumPeriodAsMonth - PremiumPlanInfoOptionsSnapshot.Value.CanBeExtendedAfterHowManyMonths))) + @if (premiumPlanInfo.IsExtendable && Model.PremiumEndDate!.Value.IsBetween(DateTime.Now, DateTime.Now.AddMonths(premiumPlanInfo.OnePremiumPeriodAsMonth!.Value - premiumPlanInfo.CanBeExtendedAfterHowManyMonths!.Value))) {
@@ -38,7 +41,7 @@ { } diff --git a/src/EventHub.Web/Pages/Payment/PlanInfoHelper.cs b/src/EventHub.Web/Pages/Payment/PlanInfoHelper.cs new file mode 100644 index 0000000..9b1346b --- /dev/null +++ b/src/EventHub.Web/Pages/Payment/PlanInfoHelper.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using EventHub.Organizations; +using Volo.Abp; + +namespace EventHub.Web.Pages.Payment; + +public static class PlanInfoHelper +{ + public static PlanInfoDefinitionDto GetPlan(OrganizationPlanType planType, List plans) + { + return plans.Single(x => x.PlanType == planType); + } + + public static string GetProductId(PlanInfoDefinitionDto premiumPlan, bool isExtend) + { + var productIdStringBuilder = new StringBuilder(OrganizationConsts.ProductNamePrefix); + productIdStringBuilder.Append("-"); + productIdStringBuilder.Append(premiumPlan.PlanType.ToString()); + productIdStringBuilder.Append("-"); + + if (isExtend) + { + if (!premiumPlan.IsExtendable) + { + throw new BusinessException(); + } + + + productIdStringBuilder.Append("Extend"); + } + else + { + productIdStringBuilder.Append("Upgrade"); + } + + return productIdStringBuilder.ToString(); + } + + public static string GetProductName(PlanInfoDefinitionDto premiumPlan, string organizationName, bool isExtend) + { + var productNameStringBuilder = new StringBuilder(OrganizationConsts.ProductNamePrefix); + productNameStringBuilder.Append(" "); + productNameStringBuilder.Append(premiumPlan.PlanType.ToString()); + productNameStringBuilder.Append(" "); + + if (isExtend) + { + if (!premiumPlan.IsExtendable) + { + throw new BusinessException(); + } + + + productNameStringBuilder.Append("Extend"); + } + else + { + productNameStringBuilder.Append("Upgrade"); + } + + productNameStringBuilder.Append(" | "); + productNameStringBuilder.Append(organizationName); + + return productNameStringBuilder.ToString(); + } + + public static string GetRestrictionInfoByCount(uint? count) + { + return count is null ? "Unlimited" : count.ToString(); + } +} diff --git a/src/EventHub.Web/Pages/Payment/PreCheckout.cshtml b/src/EventHub.Web/Pages/Payment/PreCheckout.cshtml index 5c74935..b4b36ad 100644 --- a/src/EventHub.Web/Pages/Payment/PreCheckout.cshtml +++ b/src/EventHub.Web/Pages/Payment/PreCheckout.cshtml @@ -1,13 +1,12 @@ @page @inherits Payment.Web.Pages.PaymentPageBase @using EventHub.Organizations -@using EventHub.Web.PaymentRequests +@using EventHub.Web.Pages.Payment @using Microsoft.AspNetCore.Http -@using Microsoft.Extensions.Options @using Newtonsoft.Json @using Payment.Web.Pages.Payment @model Payment.Web.Pages.Payment.PreCheckoutPageModel -@inject IOptionsSnapshot PremiumPlanInfoOptionsSnapshot +@inject IOrganizationAppService OrganizationAppService @{ var isExistExtraProperties = Model.PaymentRequest.ExtraProperties.TryGetValue(nameof(OrganizationPaymentRequestExtraParameterConfiguration), out var ExtraProperties); @@ -22,7 +21,7 @@ throw new BadHttpRequestException(""); } var organizationName = organizationPaymentRequestExtraParameterConfiguration.OrganizationName; - var planInfo = PremiumPlanInfoOptionsSnapshot.Value; + var planInfo = PlanInfoHelper.GetPlan(organizationPaymentRequestExtraParameterConfiguration.TargetPlanType, await OrganizationAppService.GetPlanInfosAsync()); } @section scripts { @@ -38,14 +37,14 @@

Product Detail

@OrganizationPlanType.Premium Account: @organizationName.ToUpperInvariant()

- @if (!organizationPaymentRequestExtraParameterConfiguration.IsExtend) + @if (!organizationPaymentRequestExtraParameterConfiguration.IsExtend && planInfo.IsExtendable) {

Start Date: @DateTime.Now.ToShortDateString()

-

End Date: @DateTime.Now.AddMonths(planInfo.OnePremiumPeriodAsMonth).ToShortDateString()

+

End Date: @DateTime.Now.AddMonths(planInfo.OnePremiumPeriodAsMonth!.Value).ToShortDateString()

} else { -

Your license will be extended for @planInfo.OnePremiumPeriodAsMonth months.

+

Your license will be extended for @planInfo.OnePremiumPeriodAsMonth!.Value months.

}
diff --git a/src/EventHub.Web/Pages/Pricing.cshtml b/src/EventHub.Web/Pages/Pricing.cshtml index 59278b0..b655d98 100644 --- a/src/EventHub.Web/Pages/Pricing.cshtml +++ b/src/EventHub.Web/Pages/Pricing.cshtml @@ -1,11 +1,9 @@ @page "/pricing/{organizationName}" @using EventHub.Localization -@using EventHub.Web.PaymentRequests +@using EventHub.Web.Pages.Payment @using Microsoft.AspNetCore.Mvc.Localization -@using Microsoft.Extensions.Options @model EventHub.Web.Pages.Pricing @inject IHtmlLocalizer L -@inject IOptionsSnapshot PremiumPlanInfoOptionsSnapshot @section styles { @@ -14,89 +12,82 @@

Pricing

Simple plans for everyone

-
-
-
-
-

Free Account

-

For individuals and small organizations.

-
- $ - 0 +
+ @foreach (var plan in Model.PlanInfos) + { +
+
+
+

@plan.PlanType Account

+

@L[plan.Description]

+
+ $ + @plan.Price +
+
    +
  • @PlanInfoHelper.GetRestrictionInfoByCount(plan.Feature.MaxAllowedEventsCount) events in a year.
  • +
  • Up to @PlanInfoHelper.GetRestrictionInfoByCount(plan.Feature.MaxAllowedTracksCountInOneEvent).ToLower() tracks for an event.
  • +
  • Up to @PlanInfoHelper.GetRestrictionInfoByCount(plan.Feature.MaxAllowedAttendeesCountInOneEvent).ToLower() attendees per event.
  • + @foreach (var additionalFeatureInfo in plan.Feature.AdditionalFeatureInfos) + { +
  • @additionalFeatureInfo
  • + } +
+ @if (Model.Organization.IsPremium && plan.Price > 0) + { +
+ + + Extend Now +
+ } + else if (plan.IsExtendable) + { +
+ + + Upgrade to @plan.PlanType +
+ }
-
    -
  • 2 events in a year.
  • -
  • Up to 2 tracks for an event.
  • -
  • Up to 1,000 attendees per event.
  • -
-
-
-
- @if (Model.PremiumPlanInfo is not null) - { -
-
-
-

Premium Account

-

For those organize regular events.

-
- $ - @Model.PremiumPlanInfo.Price -
-
    -
  • Unlimited events.
  • -
  • Unlimited tracks and sessions for an event.
  • -
  • Unlimited attendees.
  • -
  • Be more prominent in the organization list.
  • -
  • Premium badge in the organization page.
  • -
- @if (Model.Organization.IsPremium) - { -
- - - Extend Now -
- } - else - { -
- - - Upgrade to @Model.PremiumPlanInfo.PlanType -
- } -
-
-
- } +
+
+ }

Frequently Asked Questions

-
-
-
-
-
-
-
- @{ - var canExtendBeforeHowManyMonths = PremiumPlanInfoOptionsSnapshot.Value.OnePremiumPeriodAsMonth - PremiumPlanInfoOptionsSnapshot.Value.CanBeExtendedAfterHowManyMonths; - } -

@(canExtendBeforeHowManyMonths == PremiumPlanInfoOptionsSnapshot.Value.OnePremiumPeriodAsMonth ? "You " : "In the last " + canExtendBeforeHowManyMonths + " months of your premium period, you") can renew your plan by going to the Organization detail page. -

-
-
-
-
-
-
-
-
-
-

Don't worry about even if your organization has run out of premium, you can upgrade back to the Premium plan at any time. -

-
-
-
-
+
+
+
+
+
+
+
+ @foreach (var plan in Model.PlanInfos.Where(x => x.Price > 0)) + { + @if (plan.IsExtendable) + { + var canExtendBeforeHowManyMonths = plan!.OnePremiumPeriodAsMonth - plan!.CanBeExtendedAfterHowManyMonths; +

For @plan.PlanType, @(canExtendBeforeHowManyMonths == plan!.OnePremiumPeriodAsMonth ? "You " : "in the last " + canExtendBeforeHowManyMonths + " months of your " + plan.PlanType.ToString().ToLower() + " period, you") can renew your plan by going to the Organization detail page.

+ } + else + { +

For @plan.PlanType, when your @plan.PlanType period is over, you can buy again your plan by going to the Organization detail page.

+ } + + } +
+
+
+
+
+
+
+
+
+

Don't worry about even if your organization has run out of premium, you can upgrade back to the Premium plan at any time. +

+
+
+
+
diff --git a/src/EventHub.Web/Pages/Pricing.cshtml.cs b/src/EventHub.Web/Pages/Pricing.cshtml.cs index dde76ac..9e2c559 100644 --- a/src/EventHub.Web/Pages/Pricing.cshtml.cs +++ b/src/EventHub.Web/Pages/Pricing.cshtml.cs @@ -1,9 +1,9 @@ +using System.Collections.Generic; using System.Threading.Tasks; using EventHub.Organizations; -using EventHub.Web.PaymentRequests; +using EventHub.Web.Pages.Payment; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Payment.PaymentRequests; using Payment.Web.PaymentRequest; using Volo.Abp.Authorization; @@ -21,26 +21,24 @@ namespace EventHub.Web.Pages [BindProperty] public OrganizationPlanType TargetPlanToUpgrade { get; set; } - [CanBeNull] - public PremiumPlanInfoOptions PremiumPlanInfo { get; private set; } + [NotNull] + public List PlanInfos { get; private set; } public OrganizationProfileDto Organization { get; private set; } private readonly IOrganizationAppService _organizationAppService; private readonly IPaymentRequestAppService _paymentRequestAppService; private readonly IPaymentUrlBuilder _paymentUrlBuilder; - private readonly IOptionsSnapshot _premiumPlanInfoOptionsSnapshot; public Pricing( IOrganizationAppService organizationAppService, IPaymentRequestAppService paymentRequestAppService, - IPaymentUrlBuilder paymentUrlBuilder, - IOptionsSnapshot premiumPlanInfoOptionsSnapshot) + IPaymentUrlBuilder paymentUrlBuilder) { _organizationAppService = organizationAppService; _paymentRequestAppService = paymentRequestAppService; _paymentUrlBuilder = paymentUrlBuilder; - _premiumPlanInfoOptionsSnapshot = premiumPlanInfoOptionsSnapshot; + PlanInfos = new List(); } public async Task OnGetAsync() @@ -50,19 +48,16 @@ namespace EventHub.Web.Pages { throw new AbpAuthorizationException(); } - - if (_premiumPlanInfoOptionsSnapshot.Value.IsActive) - { - PremiumPlanInfo = _premiumPlanInfoOptionsSnapshot.Value; - } + PlanInfos = await _organizationAppService.GetPlanInfosAsync(); } - public async Task OnPostUpgradeAsync() + public async Task OnPostUpgradeToPremiumAsync() { var organization = await GetOrganizationProfileAsync(); + PlanInfos = await _organizationAppService.GetPlanInfosAsync(); - var plan = _premiumPlanInfoOptionsSnapshot.Value; + var plan = PlanInfoHelper.GetPlan(OrganizationPlanType.Premium, PlanInfos); if (plan is null || !plan.IsActive) { Alerts.Danger("Premium plan is currently inactive!"); @@ -74,11 +69,13 @@ namespace EventHub.Web.Pages return Redirect(_paymentUrlBuilder.BuildCheckoutUrl(paymentRequest.Id).AbsoluteUri); } - public async Task OnPostExtendAsync() + public async Task OnPostExtendToPremiumAsync() { var organization = await GetOrganizationProfileAsync(); - var plan = _premiumPlanInfoOptionsSnapshot.Value; + PlanInfos = await _organizationAppService.GetPlanInfosAsync(); + + var plan = PlanInfoHelper.GetPlan(OrganizationPlanType.Premium, PlanInfos); if (plan is null || !plan.IsActive) { Alerts.Danger("Premium plan is currently inactive!"); @@ -102,7 +99,7 @@ namespace EventHub.Web.Pages } private async Task CreatePaymentRequestAsync( - PremiumPlanInfoOptions plan, + PlanInfoDefinitionDto plan, OrganizationProfileDto organization, bool isExtend = false) { @@ -110,8 +107,8 @@ namespace EventHub.Web.Pages { CustomerId = CurrentUser.GetId().ToString(), Price = plan.Price, - ProductId = PremiumPlanInfoOptions.GetProductId(plan, isExtend), - ProductName = PremiumPlanInfoOptions.GetProductName(plan, organization.Name, isExtend), + ProductId = PlanInfoHelper.GetProductId(plan, isExtend), + ProductName = PlanInfoHelper.GetProductName(plan, organization.Name, isExtend), ExtraProperties = { { @@ -120,7 +117,8 @@ namespace EventHub.Web.Pages { OrganizationName = OrganizationName, PremiumPeriodAsMonth = plan.OnePremiumPeriodAsMonth, - IsExtend = isExtend + IsExtend = isExtend, + TargetPlanType = TargetPlanToUpgrade } } } @@ -129,4 +127,4 @@ namespace EventHub.Web.Pages return paymentRequest; } } -} \ No newline at end of file +} diff --git a/src/EventHub.Web/PaymentRequests/PremiumPlanInfoOptions.cs b/src/EventHub.Web/PaymentRequests/PremiumPlanInfoOptions.cs deleted file mode 100644 index 3b2cef2..0000000 --- a/src/EventHub.Web/PaymentRequests/PremiumPlanInfoOptions.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Text; -using EventHub.Organizations; -using Volo.Abp; - -namespace EventHub.Web.PaymentRequests -{ - public class PremiumPlanInfoOptions - { - private const string ProductNamePrefix = "EventHub"; - - public const string OrganizationPlanInfo = "PlanInfo:Premium"; - - public OrganizationPlanType PlanType { get; private set; } = OrganizationPlanType.Premium; - - [DefaultValue(false)] - public bool IsActive { get; set; } - - [Range(1.0, 100.0, ErrorMessage = "Value for {0} must be between {1} and {2}.")] - public decimal Price { get; set; } = 1.0M; - - [DefaultValue(false)] - public bool IsExtendable { get; set; } - - [Range(0, 12, ErrorMessage = "Value for {0} must be between {1} and {2}.")] - public int CanBeExtendedAfterHowManyMonths { get; set; } - - [Range(1, 24, ErrorMessage = "Value for {0} must be between {1} and {2}.")] - public int OnePremiumPeriodAsMonth { get; set; } = 12; - - public static string GetProductId(PremiumPlanInfoOptions premiumPlan, bool isExtend) - { - var productIdStringBuilder = new StringBuilder(ProductNamePrefix); - productIdStringBuilder.Append("-"); - productIdStringBuilder.Append(premiumPlan.PlanType.ToString()); - productIdStringBuilder.Append("-"); - - if (isExtend) - { - if (!premiumPlan.IsExtendable) - { - throw new BusinessException(); - } - - - productIdStringBuilder.Append("Extend"); - } - else - { - productIdStringBuilder.Append("Upgrade"); - } - - return productIdStringBuilder.ToString(); - } - - public static string GetProductName(PremiumPlanInfoOptions premiumPlan, string organizationName, bool isExtend) - { - var productNameStringBuilder = new StringBuilder(ProductNamePrefix); - productNameStringBuilder.Append(" "); - productNameStringBuilder.Append(premiumPlan.PlanType.ToString()); - productNameStringBuilder.Append(" "); - - if (isExtend) - { - if (!premiumPlan.IsExtendable) - { - throw new BusinessException(); - } - - - productNameStringBuilder.Append("Extend"); - } - else - { - productNameStringBuilder.Append("Upgrade"); - } - - productNameStringBuilder.Append(" | "); - productNameStringBuilder.Append(organizationName); - - return productNameStringBuilder.ToString(); - } - } -} \ No newline at end of file diff --git a/src/EventHub.Web/appsettings.json b/src/EventHub.Web/appsettings.json index 9758b07..2c4a520 100644 --- a/src/EventHub.Web/appsettings.json +++ b/src/EventHub.Web/appsettings.json @@ -7,14 +7,5 @@ "RequireHttpsMetadata": "true", "ClientId": "EventHub_Web", "ClientSecret": "1q2w3e*" - }, - "PlanInfo": { - "Premium": { - "IsActive": true, - "Price": 29.90, - "IsExtendable": true, - "CanBeExtendedAfterHowManyMonths": 9, - "OnePremiumPeriodAsMonth": 12 - } } }