diff --git a/.gitignore b/.gitignore index 120f14ad..e350d718 100644 --- a/.gitignore +++ b/.gitignore @@ -148,40 +148,6 @@ Web/Resgrid.WebCore/appsettings.Azure.json Web/Resgrid.WebCore/appsettings.Development.json Web/Resgrid.WebCore/appsettings.Production.json Web/Resgrid.WebCore/appsettings.Staging.json -Web/Resgrid.WebCore/Areas/User/Controllers/SubscriptionController.cs -Web/Resgrid.WebCore/Areas/User/Models/Subscription/* -Web/Resgrid.WebCore/Areas/User/Views/Subscription/* -Web/Resgrid.WebCore/Controllers/AffiliateController.cs -Web/Resgrid.WebCore/Controllers/DeveloperController.cs -Web/Resgrid.WebCore/Controllers/ForController.cs -Web/Resgrid.WebCore/Controllers/JobsController.cs -Web/Resgrid.WebCore/Controllers/ManageController.cs -Web/Resgrid.WebCore/Controllers/PublicController.cs -Web/Resgrid.WebCore/Views/Affiliate/* -Web/Resgrid.WebCore/Views/Developer/* -Web/Resgrid.WebCore/Views/For/* -Web/Resgrid.WebCore/Views/Home/* -Web/Resgrid.WebCore/Views/Jobs/* -Web/Resgrid.WebCore/Views/Manage/* -Web/Resgrid.WebCore/Views/Public/* -Web/Resgrid.WebCore/wwwroot/lib/* -Web/Resgrid.WebCore/wwwroot/css/landing/* -Web/Resgrid.WebCore/wwwroot/css/lead/* -Web/Resgrid.WebCore/wwwroot/css/patterns/* -Web/Resgrid.WebCore/wwwroot/css/pricing/* -Web/Resgrid.WebCore/wwwroot/documents/* -Web/Resgrid.WebCore/wwwroot/images/*.jpg -Web/Resgrid.WebCore/wwwroot/images/*.svg -Web/Resgrid.WebCore/wwwroot/images/apps/* -Web/Resgrid.WebCore/wwwroot/images/landing/* -Web/Resgrid.WebCore/wwwroot/images/Mobile/* -Web/Resgrid.WebCore/wwwroot/images/Personnel/* -Web/Resgrid.WebCore/wwwroot/images/Screenshots/* -Web/Resgrid.WebCore/wwwroot/images/Store/* -Web/Resgrid.WebCore/wwwroot/js/app/admin/* -Web/Resgrid.WebCore/wwwroot/videos/* -Workers/Resgrid.Workers.BackendWorkers/* -Workers/Resgrid.Workers.BroadcastWorker/* Workers/WebJobs/* Common/resgrid.com.crt Common/resgrid.local.crt @@ -302,3 +268,4 @@ Web/Resgrid.WebCore/wwwroot/js/ng/polyfills.js Web/Resgrid.WebCore/wwwroot/js/ng/runtime.js Web/Resgrid.WebCore/wwwroot/js/ng/styles.css Web/Resgrid.WebCore/wwwroot/js/ng/* +Web/Resgrid.WebCore/wwwroot/lib/* \ No newline at end of file diff --git a/Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs b/Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs index b86da87d..9b355782 100644 --- a/Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs +++ b/Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs @@ -240,7 +240,7 @@ public async Task GetCoordinatesFromW3W(string words) try { var client = new RestClient("https://api.what3words.com"); - var request = new RestRequest($"/v3/convert-to-coordinates?key={Config.MappingConfig.What3WordsApiKey}&addr={words}", Method.Get); + var request = new RestRequest($"/v3/convert-to-coordinates?key={Config.MappingConfig.What3WordsApiKey}&words={words}", Method.Get); var response = await client.ExecuteAsync(request); @@ -268,7 +268,7 @@ public async Task GetCoordinatesFromW3WAsync(string words) try { var client = new RestClient("https://api.what3words.com"); - var request = new RestRequest($"/v3/convert-to-coordinates?key={Config.MappingConfig.What3WordsApiKey}&addr={words}", Method.Get); + var request = new RestRequest($"/v3/convert-to-coordinates?key={Config.MappingConfig.What3WordsApiKey}&words={words}", Method.Get); var response = await client.ExecuteAsync(request); @@ -296,7 +296,7 @@ public async Task GetW3WFromCoordinates(Coordinates coordinates) try { var client = new RestClient("https://api.what3words.com"); - var request = new RestRequest($"/v3/convert-to-3wa?key={Config.MappingConfig.What3WordsApiKey}&coords={$"{coordinates.Latitude},{coordinates.Longitude}"}", Method.Get); + var request = new RestRequest($"/v3/convert-to-3wa?key={Config.MappingConfig.What3WordsApiKey}&coordinates={$"{coordinates.Latitude},{coordinates.Longitude}"}", Method.Get); var response = await client.ExecuteAsync(request); diff --git a/Web/Resgrid.WebCore/Areas/User/Controllers/SubscriptionController.cs b/Web/Resgrid.WebCore/Areas/User/Controllers/SubscriptionController.cs new file mode 100644 index 00000000..da0dc806 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Controllers/SubscriptionController.cs @@ -0,0 +1,940 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Resgrid.Model; +using Resgrid.Model.Helpers; +using Resgrid.Model.Services; +using Resgrid.Providers.Claims; +using Resgrid.Web.Areas.User.Models.Subscription; +using Resgrid.Web.Options; +using Stripe; +using Microsoft.AspNetCore.Authorization; +using Resgrid.Framework; +using Resgrid.Model.Events; +using Resgrid.Model.Providers; +using Resgrid.Providers.Bus; +using Resgrid.Services; +using Resgrid.Web.Helpers; + +namespace Resgrid.Web.Areas.User.Controllers +{ + [Area("User")] + [ClaimsResource(ResgridClaimTypes.Resources.Department)] + public class SubscriptionController : SecureBaseController + { + #region Private Members and Constructors + + private readonly IDepartmentsService _departmentsService; + private readonly IUsersService _usersService; + private readonly IDepartmentGroupsService _departmentGroupsService; + private readonly Model.Services.IAuthorizationService _authorizationService; + private readonly ISubscriptionsService _subscriptionsService; + private readonly IPersonnelRolesService _personnelRolesService; + private readonly IUnitsService _unitsService; + private readonly IDepartmentSettingsService _departmentSettingsService; + private readonly IEmailService _emailService; + private readonly IAffiliateService _affiliateService; + private readonly IUserProfileService _userProfileService; + private readonly IOptions _appOptionsAccessor; + private readonly IEventAggregator _eventAggregator; + + public SubscriptionController(IDepartmentsService departmentsService, IUsersService usersService, IDepartmentGroupsService departmentGroupsService, + Model.Services.IAuthorizationService authorizationService, ISubscriptionsService subscriptionsService, IPersonnelRolesService personnelRolesService, IUnitsService unitsService, + IDepartmentSettingsService departmentSettingsService, IEmailService emailService, IAffiliateService affiliateService, + IUserProfileService userProfileService, IOptions appOptionsAccessor, IEventAggregator eventAggregator) + { + _departmentsService = departmentsService; + _usersService = usersService; + _departmentGroupsService = departmentGroupsService; + _authorizationService = authorizationService; + _subscriptionsService = subscriptionsService; + _personnelRolesService = personnelRolesService; + _unitsService = unitsService; + _departmentSettingsService = departmentSettingsService; + _emailService = emailService; + _affiliateService = affiliateService; + _userProfileService = userProfileService; + _appOptionsAccessor = appOptionsAccessor; + _eventAggregator = eventAggregator; + } + + #endregion Private Members and Constructors + + [HttpGet] + +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task Index() + { + if (!await _authorizationService.CanUserManageSubscriptionAsync(UserId, DepartmentId)) + Unauthorized(); + + var model = new SubscriptionView(); + var department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId); + model.Plan = await _subscriptionsService.GetCurrentPlanForDepartmentAsync(DepartmentId); + model.Payment = await _subscriptionsService.GetCurrentPaymentForDepartmentAsync(DepartmentId); + model.IsTestingDepartment = await _departmentSettingsService.IsTestingEnabledForDepartmentAsync(DepartmentId); + model.Department = department; + model.StripeKey = Config.PaymentProviderConfig.GetStripeClientKey(); + model.StripeCustomer = await _departmentSettingsService.GetStripeCustomerIdForDepartmentAsync(DepartmentId); + + if (model.Plan != null && model.Plan.PlanId != 1 && model.Plan.Cost == 0) + { + if (model.Payment != null) + { + model.Plan.Cost = model.Payment.Amount; + model.Plan.Quantity = model.Payment.Quantity; + } + } + + var allPayments = await _subscriptionsService.GetAllPaymentsForDepartmentAsync(DepartmentId); + model.HadStripePaymentIn30Days = allPayments.Any(x => x.EndingOn >= DateTime.UtcNow.AddYears(-2) && x.Method == (int)PaymentMethods.Stripe); + + if (model.Payment != null) + { + if (model.Payment.EndingOn == DateTime.MaxValue) + model.Expires = "Never"; + else + model.Expires = TimeConverterHelper.TimeConverter(model.Payment.EndingOn, department).ToString("D"); + } + else + { + model.Expires = "Never"; + } + + if (model.Plan != null) + { + model.PossibleUpgrades = _subscriptionsService.GetPossibleUpgradesForPlan(model.Plan.PlanId); + model.PossibleDowngrades = _subscriptionsService.GetPossibleDowngradesForPlan(model.Plan.PlanId); + } + else + { + model.PossibleUpgrades = _subscriptionsService.GetPossibleUpgradesForPlan(1); + model.PossibleDowngrades = _subscriptionsService.GetPossibleUpgradesForPlan(1); + + model.Plan = new Resgrid.Model.Plan() { PlanId = 1, Cost = 0, Name = "Forever Free" }; + } + + var personnelCount = (await _departmentsService.GetAllUsersForDepartmentUnlimitedMinusDisabledAsync(DepartmentId)).Count; + var unitsCount = (await _unitsService.GetUnitsForDepartmentUnlimitedAsync(DepartmentId)).Count; + + if (model.Plan.PlanId >= 36) + { + model.PersonnelCount = personnelCount + unitsCount; + model.PersonnelLimit = model.Plan.GetLimitForType(PlanLimitTypes.Entities); + float personnelLimit; + if (float.TryParse(model.Plan.GetLimitForType(PlanLimitTypes.Entities), out personnelLimit)) + { + float personLimit = (model.PersonnelCount / personnelLimit) * 100f; + model.PersonnelBarPrecent = personLimit.ToString(); + + if (personLimit >= 100) + { + ViewBag.PersonnelBarStyle = "progress-bar-danger"; + SetSubscriptionErrorMessage(); + } + else if (personLimit >= 75) + ViewBag.PersonnelBarStyle = "progress-bar-warning"; + else + ViewBag.PersonnelBarStyle = "progress-bar-info"; + } + else + { + model.PersonnelBarPrecent = "0.0"; + } + } + else + { + model.PersonnelCount = personnelCount; + model.PersonnelLimit = model.Plan.GetLimitForType(PlanLimitTypes.Personnel); + float personnelLimit; + if (float.TryParse(model.Plan.GetLimitForType(PlanLimitTypes.Personnel), out personnelLimit)) + { + float personLimit = (model.PersonnelCount / personnelLimit) * 100f; + model.PersonnelBarPrecent = personLimit.ToString(); + + if (personLimit >= 100) + { + ViewBag.PersonnelBarStyle = "progress-bar-danger"; + SetSubscriptionErrorMessage(); + } + else if (personLimit >= 75) + ViewBag.PersonnelBarStyle = "progress-bar-warning"; + else + ViewBag.PersonnelBarStyle = "progress-bar-info"; + } + else + { + model.PersonnelBarPrecent = "0.0"; + } + } + + + var addon = await _subscriptionsService.GetPTTAddonPlanForDepartmentFromStripeAsync(DepartmentId); + + model.HasActiveSubscription = await _subscriptionsService.HasActiveSubForDepartmentFromStripeAsync(DepartmentId); + model.HasActiveAddon = addon != null; + + model.AddonFrequencyString = "month"; + if (model.Plan != null) + { + if (model.Plan.Frequency == (int)PlanFrequency.Yearly) + model.AddonFrequencyString = "year"; + else if (model.Plan.Frequency == (int)PlanFrequency.Monthly) + model.AddonFrequencyString = "month"; + } + + if (addon != null && addon.IsCancelled) + { + model.IsAddonCanceled = addon.IsCancelled; + model.AddonEndingOn = addon.EndingOn; + } + + var addonPlan = await _subscriptionsService.GetPTTAddonForCurrentSubAsync(DepartmentId); + + if (addonPlan != null) + { + model.AddonCost = addonPlan.Cost.ToString("C0", Cultures.UnitedStates); + model.AddonCost2 = (addonPlan.Cost / 2).ToString("C0", Cultures.UnitedStates); + model.AddonPlanIdToBuy = addonPlan.PlanAddonId; + } + else + model.AddonCost = "0"; + + var user = _usersService.GetUserById(UserId); + + try + { + var session = await _subscriptionsService.CreateStripeSessionForCustomerPortal(DepartmentId, model.StripeCustomer, "", user.Email, department.Name); + + if (session != null) + model.StripeCustomerPortalUrl = session.Url; + } + catch (Exception ex) + { + Logging.LogException(ex); + } + + return View(model); + } + +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task UpdateBillingInfo() + { + if (!await _authorizationService.CanUserManageSubscriptionAsync(UserId, DepartmentId)) + Unauthorized(); + + var model = new BuyNowView(); + + if (Config.PaymentProviderConfig.IsTestMode) + model.StripeKey = Config.PaymentProviderConfig.TestClientKey; + else + model.StripeKey = Config.PaymentProviderConfig.ProductionClientKey; + + return View(model); + } + + [HttpPost] + +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + [ValidateAntiForgeryToken] + public async Task UpdateBillingInfo(IFormCollection form, CancellationToken cancellationToken) + { + if (!await _authorizationService.CanUserManageSubscriptionAsync(UserId, DepartmentId)) + Unauthorized(); + + try + { + var user = _usersService.GetUserById(UserId); + var department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId); + var stripeCustomerId = await _departmentSettingsService.GetStripeCustomerIdForDepartmentAsync(DepartmentId); + + var cardToken = form["stripeToken"]; + + var cardService = new CardService(); + var customerService = new CustomerService(); + + var updateCardOptions = new CardCreateOptions(); + updateCardOptions.Source = new AnyOf(cardToken); + + Card stripeCard = await cardService.CreateAsync(stripeCustomerId, updateCardOptions, cancellationToken: cancellationToken); + + var customerOptions = new CustomerUpdateOptions + { + Email = user.Email, + Description = department.Name, + DefaultSource = stripeCard.Id + }; + + Customer stripeCustomer = await customerService.UpdateAsync(stripeCustomerId, customerOptions, cancellationToken: cancellationToken); + + var auditEvent = new AuditEvent(); + auditEvent.Before = updateCardOptions.CloneJsonToString(); + auditEvent.DepartmentId = DepartmentId; + auditEvent.UserId = UserId; + auditEvent.Type = AuditLogTypes.SubscriptionBillingInfoUpdated; + auditEvent.After = stripeCustomer.CloneJsonToString(); + auditEvent.Successful = true; + auditEvent.IpAddress = IpAddressHelper.GetRequestIP(Request, true); + auditEvent.ServerName = Environment.MachineName; + auditEvent.UserAgent = $"{Request.Headers["User-Agent"]} {Request.Headers["Accept-Language"]}"; + _eventAggregator.SendMessage(auditEvent); + + return RedirectToAction("BillingInfoUpdateSuccess", "Subscription", new { Area = "User" }); + } + catch (Exception ex) + { + Logging.SendExceptionEmail(ex, "UpdateBillingInfo", DepartmentId, UserName); + + return RedirectToAction("PaymentFailed", "Subscription", + new { Area = "User", chargeId = "", errorMessage = ex.Message }); + } + } + + [HttpPost] + + public async Task LogStripeResponse(StripeResponseInput input, CancellationToken cancellationToken) + { + var providerEvent = new PaymentProviderEvent(); + providerEvent.ProviderType = (int)PaymentMethods.Stripe; + providerEvent.RecievedOn = DateTime.UtcNow; + providerEvent.Data = $"Card Token Result: UserId:{UserId} DepartmentId:{DepartmentId} Status:{input.Status} Response:{input.Response}"; + providerEvent.Processed = false; + providerEvent.CustomerId = "SYSTEM"; + + await _subscriptionsService.SavePaymentEventAsync(providerEvent, cancellationToken); + + return new EmptyResult(); + } + + [HttpGet] +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task ValidateCoupon(string couponCode) + { + var service = new CouponService(); + Coupon coupon = null; + + try + { + if (!String.IsNullOrWhiteSpace(couponCode)) + coupon = await service.GetAsync(couponCode.Trim().ToUpper()); + } + catch + { + } + + if (coupon == null || (coupon.RedeemBy.HasValue && coupon.RedeemBy.Value < DateTime.UtcNow)) + return Content("Invalid"); + + return Content("Valid"); + } + + [HttpGet] +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task Cancel() + { + if (!await _authorizationService.CanUserManageSubscriptionAsync(UserId, DepartmentId)) + Unauthorized(); + + CancelView model = new CancelView(); + model.Payment = await _subscriptionsService.GetCurrentPaymentForDepartmentAsync((await _departmentsService.GetDepartmentByUserIdAsync(UserId)).DepartmentId); + model.Plan = await _subscriptionsService.GetPlanByIdAsync(model.Payment.PlanId); + + return View(model); + } + + [HttpGet] + public async Task BillingInfoUpdateSuccess() + { + return View(); + } + + + [HttpGet] + public async Task StripeBillingInfoUpdateSuccess(string sessionId) + { + var model = new PaymentCompleteView(); + model.SessionId = sessionId; + + return View(model); + } + + [HttpPost] +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task Cancel(CancelView model, CancellationToken cancellationToken) + { + if (!await _authorizationService.CanUserManageSubscriptionAsync(UserId, DepartmentId)) + Unauthorized(); + + if (!model.Confirm) + ModelState.AddModelError("Confirm", "You must check the confirm box to cancel the subscription."); + + if (ModelState.IsValid) + { + var payment = await _subscriptionsService.GetCurrentPaymentForDepartmentAsync(DepartmentId); + if (payment == null) + return RedirectToAction("CancelFailure", "Subscription", new { Area = "User" }); + + if (payment.Method == (int)PaymentMethods.Stripe) + { + var stripeCustomerId = await _departmentSettingsService.GetStripeCustomerIdForDepartmentAsync(DepartmentId); + + if (String.IsNullOrWhiteSpace(stripeCustomerId)) + { + var user = _usersService.GetUserById(UserId); + var cusService = new CustomerService(); + var options = new CustomerListOptions + { + Email = user.Email + }; + + var customerList = await cusService.ListAsync(options, cancellationToken: cancellationToken); + + if (customerList != null && customerList.Any()) + stripeCustomerId = customerList.First().Id; + } + + if (!String.IsNullOrWhiteSpace(stripeCustomerId)) + { + var subscriptionService = new SubscriptionService(); + var subs = await subscriptionService.ListAsync(new SubscriptionListOptions { Customer = stripeCustomerId }, cancellationToken: cancellationToken); + Subscription subscription = subs.First(sub => !sub.EndedAt.HasValue); + + var cancelledSub = await subscriptionService.CancelAsync(subscription.Id, new SubscriptionCancelOptions { }, cancellationToken: cancellationToken); + + var auditEvent = new AuditEvent(); + auditEvent.Before = JsonConvert.SerializeObject(subscription); + auditEvent.DepartmentId = DepartmentId; + auditEvent.UserId = UserId; + auditEvent.Type = AuditLogTypes.SubscriptionCancelled; + auditEvent.After = JsonConvert.SerializeObject(cancelledSub); + auditEvent.Successful = true; + auditEvent.IpAddress = IpAddressHelper.GetRequestIP(Request, true); + auditEvent.ServerName = Environment.MachineName; + auditEvent.UserAgent = $"{Request.Headers["User-Agent"]} {Request.Headers["Accept-Language"]}"; + _eventAggregator.SendMessage(auditEvent); + + if (cancelledSub != null && cancelledSub.Status.Equals("canceled", StringComparison.InvariantCultureIgnoreCase)) + { + return RedirectToAction("CancelSuccess", "Subscription", new { Area = "User" }); + } + else + { + return RedirectToAction("CancelFailure", "Subscription", new { Area = "User" }); + } + } + else + { + return RedirectToAction("CancelFailure", "Subscription", new { Area = "User" }); + } + + } + } + + model.Payment = await _subscriptionsService.GetCurrentPaymentForDepartmentAsync((await _departmentsService.GetDepartmentByUserIdAsync(UserId)).DepartmentId); + model.Plan = await _subscriptionsService.GetPlanByIdAsync(model.Payment.PlanId); + + return View(model); + } + +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task BuyAddon(string planAddonId) + { + var model = new BuyAddonView(); + model.PlanAddon = await _subscriptionsService.GetPlanAddonByIdAsync(planAddonId); + model.PlanAddonId = model.PlanAddon.PlanAddonId; + model.Department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId); + var addonTypes = await _subscriptionsService.GetAllAddonPlansAsync(); + + var addons = await _subscriptionsService.GetCurrentPaymentAddonsForDepartmentAsync(DepartmentId, + addonTypes.Where(x => x.AddonType == model.PlanAddon.AddonType).Select(y => y.PlanAddonId).ToList()); + + if (addons != null && addons.Count > 0) + model.CurrentPaymentAddon = addons.FirstOrDefault(); + + if (model.PlanAddon.PlanId.HasValue) + { + var plan = await _subscriptionsService.GetPlanByIdAsync(model.PlanAddon.PlanId.Value); + model.Frequency = ((PlanFrequency)plan.Frequency).ToString(); + } + + return View(model); + } + +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task ManagePTTAddon() + { + var model = new BuyAddonView(); + model.PlanAddon = await _subscriptionsService.GetPlanAddonByIdAsync("6f4c5f8b-584d-4291-8a7d-29bf97ae6aa9"); + model.PlanAddonId = model.PlanAddon.PlanAddonId; + model.Department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId); + + //var addons = await _subscriptionsService.GetCurrentPaymentAddonsForDepartmentAsync(DepartmentId, + // new List(){SubscriptionsService.PTT10UserAddonPackage}); + + var stripeCustomer = await _departmentSettingsService.GetStripeCustomerIdForDepartmentAsync(DepartmentId); + + var addon = await _subscriptionsService.GetActivePTTStripeSubscriptionAsync(stripeCustomer); + + if (addon != null) + { + model.Quantity = addon.TotalQuantity; + } + +/* + if (addons != null && addons.Count > 0) + model.CurrentPaymentAddon = addons.FirstOrDefault(); + + var planAddons = await _subscriptionsService.GetCurrentPlanAddonsForDepartmentFromStripeAsync(DepartmentId); + + if (planAddons != null && planAddons.Any()) + { + foreach (var addon in planAddons) + { + if (!addon.IsCancelled) + model.Quantity += addon.Quantity; + } + } + + if (model.PlanAddon.PlanId.HasValue) + { + var plan = await _subscriptionsService.GetPlanByIdAsync(model.PlanAddon.PlanId.Value); + model.Frequency = ((PlanFrequency)plan.Frequency).ToString(); + } + + */ + + return View(model); + } + + [HttpPost] + #if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] + #else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] + #endif + public async Task ManagePTTAddon(BuyAddonView model) + { + try + { + var user = _usersService.GetUserById(UserId); + + var addonPlan = await _subscriptionsService.GetPlanAddonByIdAsync(model.PlanAddonId); + var plan = await _subscriptionsService.GetPlanByIdAsync(addonPlan.PlanId.Value); + + + + var result = await _subscriptionsService.AddAddonAddedToExistingSub(DepartmentId, plan, addonPlan); + + return RedirectToAction("PaymentComplete", "Subscription", new { Area = "User", planId = plan.PlanId }); + } + catch (Exception ex) + { + Logging.SendExceptionEmail(ex, "BuyNow", DepartmentId, UserName); + + return RedirectToAction("PaymentFailed", "Subscription", + new { Area = "User", chargeId = "", errorMessage = ex.Message }); + } + } + + [HttpPost] +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task BuyAddon(BuyAddonView model, CancellationToken cancellationToken) + { + try + { + var user = _usersService.GetUserById(UserId); + + var addonPlan = await _subscriptionsService.GetPlanAddonByIdAsync(model.PlanAddonId); + var currentAddonPayments = await _subscriptionsService.GetCurrentPlanAddonsForDepartmentFromStripeAsync(DepartmentId); + + if (addonPlan != null) + { + var stripeCustomerId = await _departmentSettingsService.GetStripeCustomerIdForDepartmentAsync(DepartmentId); + + var auditEvent = new AuditEvent(); + auditEvent.Before = null; + auditEvent.DepartmentId = DepartmentId; + auditEvent.UserId = UserId; + auditEvent.Type = AuditLogTypes.AddonSubscriptionModified; + auditEvent.After = model.Quantity.ToString(); + auditEvent.Successful = true; + auditEvent.IpAddress = IpAddressHelper.GetRequestIP(Request, true); + auditEvent.ServerName = Environment.MachineName; + auditEvent.UserAgent = $"{Request.Headers["User-Agent"]} {Request.Headers["Accept-Language"]}"; + _eventAggregator.SendMessage(auditEvent); + + var result = await _subscriptionsService.ModifyPTTAddonSubscriptionAsync(stripeCustomerId, model.Quantity, addonPlan); + + if (result) + return RedirectToAction("PaymentComplete", "Subscription", new { Area = "User", planId = 0 }); + else + return RedirectToAction("PaymentFailed", "Subscription", new { Area = "User", chargeId = "", errorMessage = "Unknown Error" }); + } + else + { + return RedirectToAction("PaymentFailed", "Subscription", new { Area = "User", chargeId = "", errorMessage = "Unknown Addon Plan" }); + } + } + catch (Exception ex) + { + Logging.SendExceptionEmail(ex, "BuyNow", DepartmentId, UserName); + + return RedirectToAction("PaymentFailed", "Subscription", + new { Area = "User", chargeId = "", errorMessage = ex.Message }); + } + } + +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task CancelAddon(int addonTypeId) + { + + switch ((PlanAddonTypes)addonTypeId) + { + case PlanAddonTypes.PTT: + var addonPttPlan = await _subscriptionsService.GetPTTAddonPlanForDepartmentFromStripeAsync(DepartmentId); + + if (addonPttPlan != null) + { + var result = await _subscriptionsService.CancelPlanAddonByTypeFromStripeAsync(DepartmentId, addonTypeId); + } + break; + default: + break; + } + + return RedirectToAction("Index", "Subscription"); + } + + +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task Upgrade(int planId, int count) + { + if (!_subscriptionsService.ValidateUserSelectableBuyNowPlan(planId)) + Unauthorized(); + + var model = new BuyNowView(); + model.Plan = await _subscriptionsService.GetPlanByIdAsync(planId); + model.PlanId = model.Plan.PlanId; + model.Department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId); + model.StripeKey = Config.PaymentProviderConfig.GetStripeClientKey(); + model.Payment = await _subscriptionsService.GetCurrentPaymentForDepartmentAsync(DepartmentId); + model.Price = model.Plan.Cost; + model.Frequency = ((PlanFrequency)model.Plan.Frequency).ToString(); + + if (model.Payment != null) + { + if (model.Payment.Plan.PlanId != 1 && model.Payment.Plan.PlanId != 7 && model.Payment.Plan.PlanId != 8 && + model.Payment.Plan.PlanId != 6 && model.Plan.PlanId != model.Payment.PlanId && + _subscriptionsService.GetPossibleUpgradesForPlan(model.Payment.PlanId).Any(x => x == model.Plan.PlanId)) + { + model.UpgradePrice = await _subscriptionsService.GetAdjustedUpgradePriceAsync(model.Payment.PaymentId, model.Plan.PlanId); + model.Price = await _subscriptionsService.GetAdjustedUpgradePriceAsync(model.Payment.PaymentId, model.Plan.PlanId); + + if (model.Plan.Frequency != model.Payment.Plan.Frequency) + model.FrequencyChange = true; + else + model.FrequencyChange = false; + + model.Upgrade = true; + model.Price = model.UpgradePrice; + + if (model.FrequencyChange) + { + var billingUpdate = _subscriptionsService.CalculateCyclesTillFirstBill(model.UpgradePrice, model.Plan.Cost); + model.BillingCycles = billingUpdate.Item1; + model.UpgradePrice = billingUpdate.Item2; + model.NextBillingCycle = + TimeConverterHelper.TimeConverter(DateTime.UtcNow, model.Department).AddMonths(model.BillingCycles); + } + + } + else if (model.Payment.Plan.PlanId != 1 && model.Payment.Plan.PlanId != 7 && model.Payment.Plan.PlanId != 8 && + model.Payment.Plan.PlanId != 6 && model.Plan.PlanId != model.Payment.PlanId && + _subscriptionsService.GetPossibleDowngradesForPlan(model.Payment.PlanId).Any(x => x == model.Plan.PlanId)) + { + model.UpgradePrice = await _subscriptionsService.GetAdjustedUpgradePriceAsync(model.Payment.PaymentId, model.Plan.PlanId); + model.Price = await _subscriptionsService.GetAdjustedUpgradePriceAsync(model.Payment.PaymentId, model.Plan.PlanId); + + if (model.Plan.Frequency != model.Payment.Plan.Frequency) + model.FrequencyChange = true; + else + model.FrequencyChange = false; + + model.Upgrade = true; + model.Price = model.UpgradePrice; + + if (model.FrequencyChange) + { + var billingUpdate = _subscriptionsService.CalculateCyclesTillFirstBill(model.UpgradePrice, model.Plan.Cost); + model.BillingCycles = billingUpdate.Item1; + model.UpgradePrice = billingUpdate.Item2; + model.NextBillingCycle = + TimeConverterHelper.TimeConverter(DateTime.UtcNow, model.Department).AddMonths(model.BillingCycles); + } + } + + } + + return View(model); + } + + [HttpPost] + +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + [ValidateAntiForgeryToken] + public async Task Upgrade(BuyNowView model, CancellationToken cancellationToken) + { + try + { + var user = _usersService.GetUserById(UserId); + + // Sneaky sneaky, did you change the plan id in the form? + if (!_subscriptionsService.ValidateUserSelectableBuyNowPlan(model.PlanId)) + Unauthorized(); + + var stripeCustomerId = await _departmentSettingsService.GetStripeCustomerIdForDepartmentAsync(DepartmentId); + var plan = await _subscriptionsService.GetPlanByIdAsync(model.PlanId); + var invoice = await _subscriptionsService.ChangeActiveSubscriptionAsync(stripeCustomerId, plan.ExternalId); + + return RedirectToAction("Processing", "Subscription", new { Area = "User", planId = plan.PlanId }); + + } + catch (Exception ex) + { + Logging.SendExceptionEmail(ex, "BuyNow", DepartmentId, UserName); + + return RedirectToAction("PaymentFailed", "Subscription", + new { Area = "User", chargeId = "", errorMessage = ex.Message }); + } + } + + [HttpGet] +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task GetStripeSession(int id, CancellationToken cancellationToken) + { + var plan = await _subscriptionsService.GetPlanByIdAsync(id); + var stripeCustomerId = await _departmentSettingsService.GetStripeCustomerIdForDepartmentAsync(DepartmentId); + var department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId); + var user = _usersService.GetUserById(UserId); + var session = await _subscriptionsService.CreateStripeSessionForSub(DepartmentId, stripeCustomerId, plan.GetExternalKey(), plan.PlanId, user.Email, department.Name); + var subscription = await _subscriptionsService.GetActiveStripeSubscriptionAsync(session.CustomerId); + + bool hasActiveSub = false; + if (subscription != null) + hasActiveSub = true; + + return Json(new + { + SessionId = session, + HasActiveSub = hasActiveSub + }); ; + } + + [HttpGet] +#if (DEBUG || DOCKER) + [Authorize(Policy = ResgridResources.Department_Update)] +#else + //[RequireHttps] + [Authorize(Policy = ResgridResources.Department_Update)] +#endif + public async Task GetStripeUpdate() + { + //var plan = await _subscriptionsService.GetPlanById(id); + var stripeCustomerId = await _departmentSettingsService.GetStripeCustomerIdForDepartmentAsync(DepartmentId); + var department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId); + var user = _usersService.GetUserById(UserId); + var session = await _subscriptionsService.CreateStripeSessionForUpdate(DepartmentId, stripeCustomerId, user.Email, department.Name); + + return Json(new + { + SessionId = session.SessionId + }); + } + + //[AuthorizeUpdate] + public async Task CancelSuccess() + { + return View(); + } + + //[AuthorizeUpdate] + public async Task CancelFailure() + { + return View(); + } + + //[AuthorizeUpdate] + public async Task PaymentComplete(int paymentId) + { + PaymentCompleteView model = new PaymentCompleteView(); + model.PaymentId = paymentId; + + return View(model); + } + + //[AuthorizeUpdate] + public async Task UnableToPurchase() + { + UnableToPurchaseView model = new UnableToPurchaseView(); + + model.CurrentPayment = await _subscriptionsService.GetCurrentPaymentForDepartmentAsync(DepartmentId); + model.NextPayment = await _subscriptionsService.GetUpcomingPaymentForDepartmentAsync(DepartmentId); + + return View(model); + } + + //[AuthorizeUpdate] + public async Task PaymentFailed(string chargeId, string errorMessage) + { + PaymentFailedView model = new PaymentFailedView(); + model.ChargeId = chargeId; + model.ErrorMessage = errorMessage; + + return View(model); + } + + public async Task PaymentPending() + { + PaymentFailedView model = new PaymentFailedView(); + + return View(model); + } + + //[AuthorizeUpdate] + public async Task PaymentHistory() + { + PaymentHistoryView model = new PaymentHistoryView(); + model.Payments = await _subscriptionsService.GetAllPaymentsForDepartmentAsync(DepartmentId); + model.Department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId); + + return View(model); + } + + //[AuthorizeUpdate] + public async Task ViewInvoice(int paymentId) + { + if (!await _authorizationService.CanUserViewPaymentAsync(UserId, paymentId)) + Unauthorized(); + + ViewInvoiceView model = new ViewInvoiceView(); + model.Payment = await _subscriptionsService.GetPaymentByIdAsync(paymentId); + + if (!String.IsNullOrWhiteSpace(model.Payment.Data)) + { + try + { + model.Charge = JsonConvert.DeserializeObject(model.Payment.Data); + } + catch { } + } + + return View(model); + } + + public async Task Processing(int planId) + { + ProcessingView model = new ProcessingView(); + model.PlanId = planId; + + return View(model); + } + + public async Task StripeProcessing(int planId, string sessionId) + { + ProcessingView model = new ProcessingView(); + model.PlanId = planId; + model.SessionId = sessionId; + + return View(model); + } + + [HttpGet] + public async Task CheckProcessingStatus(int planId) + { + var payment = await _subscriptionsService.GetCurrentPaymentForDepartmentAsync(DepartmentId); + + if (payment != null && payment.PlanId == planId && payment.PurchaseOn.ToShortDateString() == DateTime.UtcNow.ToShortDateString()) + return Json("1"); + + return Json("0"); + } + + private void SetSubscriptionErrorMessage() + { + ViewBag.SubscriptionErrorMessage = + "It appears that you have more entities then your current plan allows. Don't worry they have not been deleted, but to re-enable access to them you need to purchase a higher plan. Note that users, groups or units that are the the ones past the limit (by date added) may not be visible or able to use the system."; + } + } +} diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/BuyAddonView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/BuyAddonView.cs new file mode 100644 index 00000000..edfd26e1 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/BuyAddonView.cs @@ -0,0 +1,28 @@ +using System; +using Resgrid.Model; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class BuyAddonView : BaseUserModel + { + public Department Department { get; set; } + public string Message { get; set; } + public string PlanAddonId { get; set; } + public PlanAddon PlanAddon { get; set; } + public string AddonName { get; set; } + public PaymentAddon CurrentPaymentAddon { get; set; } + + + public bool Upgrade { get; set; } + public Payment Payment { get; set; } + public double UpgradePrice { get; set; } + public double Price { get; set; } + public string Frequency { get; set; } + public bool FrequencyChange { get; set; } + public string StripeKey { get; set; } + public string BrainTreeClientToken { get; set; } + public int BillingCycles { get; set; } + public DateTime? NextBillingCycle { get; set; } + public long Quantity { get; set; } + } +} diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/BuyNowView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/BuyNowView.cs new file mode 100644 index 00000000..1befc36d --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/BuyNowView.cs @@ -0,0 +1,23 @@ +using System; +using Resgrid.Model; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class BuyNowView : BaseUserModel + { + public Department Department { get; set; } + public string Message { get; set; } + public int PlanId { get; set; } + public Plan Plan { get; set; } + public bool Upgrade { get; set; } + public Payment Payment { get; set; } + public double UpgradePrice { get; set; } + public double Price { get; set; } + public string Frequency { get; set; } + public bool FrequencyChange { get; set; } + public string StripeKey { get; set; } + public string BrainTreeClientToken { get; set; } + public int BillingCycles { get; set; } + public DateTime? NextBillingCycle { get; set; } + } +} \ No newline at end of file diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/CancelView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/CancelView.cs new file mode 100644 index 00000000..6ee0c521 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/CancelView.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using Resgrid.Model; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class CancelView + { + public Plan Plan { get; set; } + public Payment Payment { get; set; } + + public string Reason { get; set; } + + [Required] + public bool Confirm { get; set; } + } +} \ No newline at end of file diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentCompleteView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentCompleteView.cs new file mode 100644 index 00000000..74704693 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentCompleteView.cs @@ -0,0 +1,12 @@ +using Resgrid.Model; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class PaymentCompleteView : BaseUserModel + { + public string Message { get; set; } + public string ChargeId { get; set; } + public int PaymentId { get; set; } + public string SessionId { get; set; } + } +} diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentFailedView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentFailedView.cs new file mode 100644 index 00000000..79d53720 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentFailedView.cs @@ -0,0 +1,11 @@ +using Resgrid.Model; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class PaymentFailedView : BaseUserModel + { + public string Message { get; set; } + public string ChargeId { get; set; } + public string ErrorMessage { get; set; } + } +} \ No newline at end of file diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentHistoryView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentHistoryView.cs new file mode 100644 index 00000000..a2e8d0a4 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentHistoryView.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Resgrid.Model; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class PaymentHistoryView : BaseUserModel + { + public Department Department { get; set; } + public string Message { get; set; } + public List Payments { get; set; } + } +} \ No newline at end of file diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentView.cs new file mode 100644 index 00000000..8aa28f45 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/PaymentView.cs @@ -0,0 +1,15 @@ +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class PaymentView + { + public string credit_card_billing_address { get; set; } + public string credit_card_billing_zip { get; set; } + public string country { get; set; } + public string credit_card_first_name { get; set; } + public string credit_card_last_name { get; set; } + public int PlanId { get; set; } + public bool IsUpgrade { get; set; } + public string coupon_code { get; set; } + public string affiliate_code { get; set; } + } +} \ No newline at end of file diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/ProcessingView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/ProcessingView.cs new file mode 100644 index 00000000..561e38b1 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/ProcessingView.cs @@ -0,0 +1,8 @@ +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class ProcessingView + { + public int PlanId { get; set; } + public string SessionId { get; set; } + } +} diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/StripeResponseInput.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/StripeResponseInput.cs new file mode 100644 index 00000000..c10abd1d --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/StripeResponseInput.cs @@ -0,0 +1,8 @@ +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class StripeResponseInput + { + public string Status { get; set; } + public string Response { get; set; } + } +} \ No newline at end of file diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/SubscriptionView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/SubscriptionView.cs new file mode 100644 index 00000000..d928eab1 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/SubscriptionView.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using Resgrid.Model; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class SubscriptionView : BaseUserModel + { + public Department Department { get; set; } + public string Message { get; set; } + public Plan Plan { get; set; } + public Payment Payment { get; set; } + public string Expires { get; set; } + public int PersonnelCount { get; set; } + public string PersonnelLimit { get; set; } + public string PersonnelBarPrecent { get; set; } + public int GroupsCount { get; set; } + public string GroupsLimit { get; set; } + public string GroupsBarPrecent { get; set; } + public int UnitsCount { get; set; } + public string UnitsLimit { get; set; } + public string UnitsBarPrecent { get; set; } + public int RolesCount { get; set; } + public string RolesLimit { get; set; } + public string RolesBarPrecent { get; set; } + public bool IsTestingDepartment { get; set; } + public List PossibleUpgrades { get; set; } + public List PossibleDowngrades { get; set; } + public bool HadStripePaymentIn30Days { get; set; } + public string StripeKey { get; set; } + public string StripeCustomer { get; set; } + + public bool HasActiveSubscription { get; set; } + public bool HasActiveAddon { get; set; } + public string AddonFrequencyString { get; set; } + public string AddonCost { get; set; } + + public string AddonCost2 { get; set; } + + public string AddonPlanIdToBuy { get; set; } + + public bool IsAddonCanceled { get; set; } + public DateTime? AddonEndingOn { get; set; } + public string StripeCustomerPortalUrl { get; set; } + + + public string IsEntitiesTabActive() + { + if (Plan == null || Plan.PlanId == 1 || Plan.PlanId >= 36) + return "active"; + + return ""; + } + + public string IsLegacyTabActive() + { + if (Plan != null && Plan.PlanId < 36 && Plan.PlanId != 1) + return "active"; + + return ""; + } + } +} diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/UnableToPurchaseView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/UnableToPurchaseView.cs new file mode 100644 index 00000000..c88482d5 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/UnableToPurchaseView.cs @@ -0,0 +1,10 @@ +using Resgrid.Model; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class UnableToPurchaseView : BaseUserModel + { + public Payment CurrentPayment { get; set; } + public Payment NextPayment { get; set; } + } +} \ No newline at end of file diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Subscription/ViewInvoiceView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/ViewInvoiceView.cs new file mode 100644 index 00000000..0e786a47 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Models/Subscription/ViewInvoiceView.cs @@ -0,0 +1,11 @@ +using Resgrid.Model; +using Stripe; + +namespace Resgrid.Web.Areas.User.Models.Subscription +{ + public class ViewInvoiceView + { + public Payment Payment { get; set; } + public Charge Charge { get; set; } + } +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/Dashboard.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/Dashboard.cshtml index baa8d0dc..9120d422 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/Dashboard.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/Dashboard.cshtml @@ -124,8 +124,6 @@ #}# - - - - - *@ + } diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Mapping/ViewType.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Mapping/ViewType.cshtml index ccc7486d..5ced8baf 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Mapping/ViewType.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Mapping/ViewType.cshtml @@ -116,7 +116,6 @@ @section Scripts { - -} \ No newline at end of file +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Shared/_UserLayout.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Shared/_UserLayout.cshtml index b20dae3a..262518fa 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Shared/_UserLayout.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Shared/_UserLayout.cshtml @@ -32,17 +32,18 @@ { @RenderSection("Styles", required: false) } - @if (Resgrid.Config.ExternalErrorConfig.SentryPerfSampleRate > 0 && !String.IsNullOrWhiteSpace(Resgrid.Config.ExternalErrorConfig.ExternalErrorServiceUrlForWebsite)) { } @@ -83,8 +84,6 @@ integrity="sha384-vhJnz1OVIdLktyixHY4Uk3OHEwdQqPppqYR8+5mjsauETgLOcEynD9oPHhhz18Nw"> - - diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BillingInfoUpdateSuccess.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BillingInfoUpdateSuccess.cshtml new file mode 100644 index 00000000..aab864ec --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BillingInfoUpdateSuccess.cshtml @@ -0,0 +1,42 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.PaymentCompleteView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["BillingUpdateSuccessHeader"]; +} + +
+
+

@localizer["BillingUpdateSuccessHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["BillingUpdateSuccessHeader"] +
+
+

@localizer["BillingUpdateSuccessInfo"]

+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BuyAddon.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BuyAddon.cshtml new file mode 100644 index 00000000..2ecc28ca --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BuyAddon.cshtml @@ -0,0 +1,96 @@ +@using Resgrid.Framework +@using Resgrid.Model +@model Resgrid.Web.Areas.User.Models.Subscription.BuyAddonView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | "+ @localizer["BuyAddonHeader"]; +} + + +
+
+

@localizer["BuyAddonHeader"]

+ +
+
+ +
+
+
+
+
+
+
+
+ @localizer["BuyAddonInfo1"] +

+ @{ + var addonCost = (Model.PlanAddon.Cost / 2).ToString("C0", Cultures.UnitedStates); + } + @Model.PlanAddon.GetAddonName() for @addonCost/@Model.Frequency +

+ +

+ @localizer["BuyAddonInfo2"] +

+ +
+
+ +
+
+ @if (!String.IsNullOrEmpty(Model.Message)) + { +
+ @Model.Message +
+ } + @Html.AntiForgeryToken() + @Html.HiddenFor(m => m.PlanAddonId) +
+
+
+ + +
+
+ @if (Model.CurrentPaymentAddon != null && Model.CurrentPaymentAddon.IsCancelled) + { +
+

@localizer["BuyAddonWarning"]

+ @localizer["BuyAddonWarningText"] @Model.CurrentPaymentAddon.EndingOn.FormatForDepartment(Model.Department, true) +
+ } +
+
+ +
+
+ @commonLocalizer["Cancel"] + +
+
+
+ +
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BuyNow.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BuyNow.cshtml new file mode 100644 index 00000000..cf8d0075 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/BuyNow.cshtml @@ -0,0 +1,600 @@ +@using Resgrid.Framework +@using Resgrid.Model +@model Resgrid.Web.Areas.User.Models.Subscription.BuyNowView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["BuyNowHeader"]; +} + + +
+
+

@localizer["BuyNowHeader"]

+ +
+
+ +
+
+
+
+
+
+
+ @localizer["BuyFollowingHeader"] + @if (Model.Plan.Frequency == (int)PlanFrequency.Yearly) + { +

+ @Model.Plan.Name @localizer["For"] $@Model.Plan.Cost.ToString()/@localizer["Year"] +

+ } + else + { +

+ @Model.Plan.Name @localizer["For"] $@Model.Plan.Cost.ToString()/@localizer["Month"] +

+ } + + @if (Model.Payment != null && Model.FrequencyChange && Model.Payment.Plan.Frequency == (int)PlanFrequency.Yearly && Model.Plan.Frequency == (int)PlanFrequency.Monthly) + { +

+ @localizer["NextPaymentWIllBeOn"] for usd +

+

@localizer["SwitchingFromYearly"]

+ } + else if (Model.Payment != null && Model.Payment.Plan.Frequency != (int)PlanFrequency.Never) + { +

+ @localizer["EstimatedTotal"] usd +

+ @localizer["EstimatedTotalWarning"] + } + else + { +

+ Your total is usd +

+ } +
+
+ + +
+
+ @if (!String.IsNullOrEmpty(Model.Message)) + { +
+ @Model.Message +
+ } + @Html.AntiForgeryToken() + @Html.HiddenFor(m => m.PlanId) +
+
+
+ + +

+ @localizer["CreditCardBillingHeader"] +

+ + + + +
+ +
+ + + @localizer["BillingStreetWarning"] +
+
+ + +
+
+
+ +
+
+
+ +
+ +
+ + +
+
+ + +
+ @localizer["SSLInfo"] +
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + @localizer["CCVCodeInfo"] (@localizer["CCVCodeMoreInfo"]) +
+
+ +
+
+ +
+
+
+ +   + + +
+
+
+
+
+ + +
+
+ @commonLocalizer["Cancel"] + +
+
+
+
+
+ +
+
+ +
+
+ + + + + + +
+
+ +
+
+
+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Cancel.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Cancel.cshtml new file mode 100644 index 00000000..bce69ad9 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Cancel.cshtml @@ -0,0 +1,103 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.CancelView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["CancelSubscriptionHeader"]; +} +@section Styles +{ + +} + +
+
+

@localizer["CancelSubscriptionHeader"]

+ +
+
+ +
+
+
+
+
+
+ +
+
+

+ @localizer["CancelSubscriptionInfo1"] +

+ @Html.AntiForgeryToken() +
+
+
+ +
+
+
@localizer["CancelSubscriptionInfoData"]
+
+
+ +
+ +
+
+
+
+
@localizer["CurrentPlan"]
+
@Model.Plan.Name
+
@localizer["BoughtOn"]
+
@Model.Payment.PurchaseOn
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ @Html.TextAreaFor(m => m.Reason, new { id = "editor", rows = "10", cols = "30", style = "width:300px;height:150px" }) +
+
+
+
+ + +
+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/CancelFailure.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/CancelFailure.cshtml new file mode 100644 index 00000000..13a549b9 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/CancelFailure.cshtml @@ -0,0 +1,41 @@ +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["CancelFailureHeader"]; +} + +
+
+

@localizer["CancelFailureHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["CancelFailureHeader"] +
+
+

@localizer["CancelFailureInfo"]

+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/CancelSuccess.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/CancelSuccess.cshtml new file mode 100644 index 00000000..07404930 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/CancelSuccess.cshtml @@ -0,0 +1,41 @@ +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | "+ @localizer["CancelSuccessHeader"]; +} + +
+
+

@localizer["CancelSuccessHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["CancelSuccessHeader"] +
+
+

@localizer["CancelSuccessInfo"]

+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Index.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Index.cshtml new file mode 100644 index 00000000..932ca834 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Index.cshtml @@ -0,0 +1,626 @@ +@using Resgrid.Framework +@using Resgrid.Model +@using Resgrid.Config +@model Resgrid.Web.Areas.User.Models.Subscription.SubscriptionView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["SubscriptionHeader"]; +} + +@section Styles +{ + + + +} + +
+
+

@localizer["SubscriptionHeader"]

+ +
+ @if (ClaimsAuthorizationHelper.IsUserDepartmentAdmin()) + { +
+
+ @if (!string.IsNullOrWhiteSpace(Model.StripeCustomer)) + { + @localizer["ManageYourSubscription"] + } +
+
+ } +
+ +
+
+
+
+
+
+
+

+ @Model.Plan.Name + + @if (Model.Payment.Cancelled) + { + @localizer["Canceled"] + } + else + { + @localizer["Active"] + } +

+ @if (Model.Payment.Cancelled) + { + @localizer["PlanExpires"]: @Model.Expires + } + else + { + @localizer["PlanRenews"]: @Model.Expires + } +
+
+
+
+

+ @if (Model.Plan.Frequency == (int)PlanFrequency.Yearly) + { + + @Model.Plan.Cost.ToString("C", Cultures.UnitedStates)/@localizer["Year"] + + } + else + { + + @Model.Plan.Cost.ToString("C", Cultures.UnitedStates)/@localizer["month"] + + } +

+
+
+
+
+

+ @Model.PersonnelCount/@Model.PersonnelLimit + @localizer["Entities"] +

+
+
+
+
+
+ + @if (ViewBag.SubscriptionErrorMessage != null) + { +
+
+
+

@localizer["Warning"]

+ @ViewBag.SubscriptionErrorMessage +
+
+
+ } +
+ + +
+
+
+
+
+ +
+
+
+
+
+ + @if (Model.Plan == null || Model.Plan.PlanId == 1) + { +
+ +
+ +
+

Entities

+ Users or Units sold in packs of 10 +
+
+
+
+ +
+
+
+

10

Entities +
+
+
+ + Monthly billing amount +
+ +

+ .00 +
+
+
+ + Yearly (annual) billing amount +
+ +

+ .00 +
+
+
+ + +
+ +
+ +
+ } + else if (Model.Plan.PlanId >= 36) + { +
+

Active Subscription

+ You currently have an activate subscription, thank you! To update the amount of entities you need, update your billing information, or cancel your subscription please use the "Manage Your Subscription" button. If you are a Invoice customer, please contact us to update your subscription. +
+ +
+ @if (!string.IsNullOrWhiteSpace(Model.StripeCustomer)) + { + @localizer["ManageYourSubscription"] + } +
+ } + else if (Model.Plan.PlanId != 1) + { +
+

Active Legacy Subscription

+ You currently have an active legacy subscription. To change over to the Entity based subscription, please cancel your current subscription and then sign up for a new one. If you are a Invoice customer, please contact us to update your subscription. Click on the "Legacy Plan" tab to see more information about your current plan. +
+ } +
+
+
+
+
+

@localizer["Warning"]

+ You are on a Legacy Plan. We do not offer these plans anymore. You can stay on this plan as long as you wish, but you will not be able to upgrade or downgrade your plan. If you wish to change your plan, you will need to cancel your current plan and then sign up for a new plan. We do not raise prices on departments with active billing relationships, so you can stay on this plan as long as you wish, you will need to use the "Change Billing Info" button below if you are using a Credit Card to pay. If you bill via Invoice your cost will stay the same if you renew. +
+ +
+ @if (!string.IsNullOrWhiteSpace(Model.StripeCustomer)) + { + @localizer["ChangeBillingInfo"] + @localizer["CancelSub"] + } +
+
+
+
+ +
+
+
+
+
+ + @if (Model.HasActiveSubscription) + { +
+
+
+
+
+
+
+

Push-To-Talk Addon

+
+
+
+
+
    +
  • Two-Way IP Voice Communications
  • +
  • Up to 50 Channels
  • +
  • Radios with worldwide reach
  • +
  • Real-Time Voice, Push-To-Talk
  • +
  • Android, iOS and Web
  • +
+
+
+
+ +

$35/mo

per 10 user pack +
+
+
+

The Push-To-Talk Addon is only available on paid plans. You can buy as many 10 user packs as you wish, and they are billed monthly separately from your Resgrid bill. Allowing you to size up and down as you need. You can have up to 50 active people per channel. Push-To-Talk requires devices with an active Internet (data) connection and uses VOIP technology.

+ Manage PTT +
+
+
+
+
+
+
+
+ } + +
+
+
+ +
+ + @localizer["BuyNowContact1"] @localizer["ContactUs"] @localizer["BuyNowContact2"] + +
+ + @section Scripts + { + + + + } diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/ManagePTTAddon.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/ManagePTTAddon.cshtml new file mode 100644 index 00000000..a381fcd5 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/ManagePTTAddon.cshtml @@ -0,0 +1,141 @@ +@using Resgrid.Framework +@using Resgrid.Model +@model Resgrid.Web.Areas.User.Models.Subscription.BuyAddonView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["BuyAddonHeader"]; +} + +@section Styles +{ + +} + +
+
+

@localizer["BuyAddonHeader"]

+ +
+
+ +
+
+
+
+
+
+
+
+ @localizer["BuyPTTAddonInfo1"] + +

+ @localizer["BuyPTTAddonInfo2"] +

+ +
+
+ +
+
+ @if (!String.IsNullOrEmpty(Model.Message)) + { +
+ @Model.Message +
+ } + @Html.AntiForgeryToken() + @Html.HiddenFor(m => m.PlanAddonId) +
+ + @Html.TextBoxFor(m => m.Quantity, new { @class = "form-control numbersOnly", autofocus = "autofocus", type = "number", min = "0", step = "1", width = "50px" }) + +
+
+ +
+
+

+ x Ten Concurrent Push to Talk User Packs for a total x users costing $10 a month +

+
+
+ +
+
+
+ If you agree to the changes press save, else use the browsers back button or press @commonLocalizer["Cancel"] +
+
+
+ +
+
+ @commonLocalizer["Cancel"] + +
+
+
+ +
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentComplete.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentComplete.cshtml new file mode 100644 index 00000000..07c9a29b --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentComplete.cshtml @@ -0,0 +1,42 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.PaymentCompleteView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["PaymentCompleteHeader"]; +} + +
+
+

@localizer["PaymentCompleteHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["PaymentCompleteHeader"] +
+
+

@localizer["PaymentCompleteBody"]

+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentFailed.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentFailed.cshtml new file mode 100644 index 00000000..2463bdc3 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentFailed.cshtml @@ -0,0 +1,42 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.PaymentFailedView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["PaymentFailedHeader"]; +} + +
+
+

@localizer["PaymentFailedHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["PaymentFailedHeader"] +
+
+

@localizer["PaymentFailedBody"]

+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentHistory.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentHistory.cshtml new file mode 100644 index 00000000..936cd0b4 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentHistory.cshtml @@ -0,0 +1,100 @@ +@using Resgrid.Framework +@using Resgrid.Model +@model Resgrid.Web.Areas.User.Models.Subscription.PaymentHistoryView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["PaymentHistoryHeader"]; +} + +
+
+

@localizer["PaymentHistoryHeader"]

+ +
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + @foreach (var p in Model.Payments) + { + + + + + + + + + + + } + + +
@localizer["PurchaseDate"]@localizer["Method"]@localizer["TransactionId"]@localizer["Plan"]@localizer["Cost"]@localizer["StartDate"]@localizer["EndDate"]
+ @p.PurchaseOn.TimeConverterToString(Model.Department) + + @if (p.Method == (int)PaymentMethods.Stripe) + { +

@localizer["Stripe"]

+ } + else if (p.Method == (int)PaymentMethods.System) + { +

@localizer["System"]

+ } + else if (p.Method == (int)PaymentMethods.PayPal) + { +

@localizer["PayPal"]

+ } +
+ @p.TransactionId + + @p.Plan.Name + + @p.Amount.ToString("C", Cultures.UnitedStates) + + @p.EffectiveOn.TimeConverter(Model.Department).ToString("D") + + @p.EndingOn.TimeConverter(Model.Department).ToString("D") + + @localizer["ViewInvoice"] +
+
+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentPending.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentPending.cshtml new file mode 100644 index 00000000..30e9880e --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/PaymentPending.cshtml @@ -0,0 +1,42 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.PaymentFailedView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["PaymentPendingHeader"]; +} + +
+
+

@localizer["PaymentPendingHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["PaymentPendingHeader"] +
+
+

@localizer["PaymentPendingBody"]

+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Processing.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Processing.cshtml new file mode 100644 index 00000000..90e30c3f --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Processing.cshtml @@ -0,0 +1,73 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.ProcessingView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["PaymentProcessingHeader"]; +} + +
+
+

@localizer["PaymentProcessingHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["PaymentProcessingHeader"] +
+
+

@localizer["PaymentProcessingBody"]

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/StripeBillingInfoUpdateSuccess.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/StripeBillingInfoUpdateSuccess.cshtml new file mode 100644 index 00000000..622ab338 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/StripeBillingInfoUpdateSuccess.cshtml @@ -0,0 +1,42 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.PaymentCompleteView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["StripeBillingInfoUpdateSuccessHeader"]; +} + +
+
+

@localizer["StripeBillingInfoUpdateSuccessHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["StripeBillingInfoUpdateSuccessHeader"] +
+
+

@localizer["StripeBillingInfoUpdateSuccessBody"]

+
+
+
+
+
+ +@section Scripts + { + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/StripeProcessing.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/StripeProcessing.cshtml new file mode 100644 index 00000000..7829e549 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/StripeProcessing.cshtml @@ -0,0 +1,73 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.ProcessingView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["StripePaymentProcessingHeader"]; +} + +
+
+

@localizer["StripePaymentProcessingHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["StripePaymentProcessingHeader"] +
+
+

@localizer["StripePaymentProcessingBody"]

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +@section Scripts + { + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/UnableToPurchase.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/UnableToPurchase.cshtml new file mode 100644 index 00000000..8b9e220e --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/UnableToPurchase.cshtml @@ -0,0 +1,52 @@ +@model Resgrid.Web.Areas.User.Models.Subscription.UnableToPurchaseView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["UnableToPurchaseHeader"]; +} + +
+
+

@localizer["UnableToPurchaseHeader"]

+ +
+
+ +
+
+
+
+
+ @localizer["UnableToPurchaseHeader"] +
+
+

@localizer["UnableToPurchaseBody"]

+
+
+ + + @if (Model.NextPayment != null && Model.NextPayment.Plan != null) + { + + } +
+
+
+
+
+
+
+ +@section Scripts + { + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/UpdateBillingInfo.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/UpdateBillingInfo.cshtml new file mode 100644 index 00000000..20398035 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/UpdateBillingInfo.cshtml @@ -0,0 +1,490 @@ +@using Resgrid.Model +@model Resgrid.Web.Areas.User.Models.Subscription.BuyNowView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["UpdateBillingInfoHeader"]; +} + +
+
+

@localizer["UpdateBillingInfoHeader"]

+ +
+
+ +
+
+
+
+
+
+ +
+
+ @if (!String.IsNullOrEmpty(Model.Message)) + { +
+ @Model.Message +
+ } + @Html.AntiForgeryToken() +
+
+
+ + +

+ Credit Card Billing Address +

+ + + + +
+ +
+ + + Must match the street address where credit card billing statements are sent +
+
+ + +
+
+
+ +
+
+
+ +
+ +
+ + +
+
+ + +
+ Payment information is secured with SSL encryption. Resgrid DOES NOT store any payment info. +
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + 3 or 4 digit number on the card (more info) +
+
+ +
+
+ +
+
+
+ +   + + +
+
+
+
+
+ + +
+
+ Cancel + +
+
+
+
+
+
+
+
+ + +@section Scripts +{ + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Upgrade.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Upgrade.cshtml new file mode 100644 index 00000000..b0caef7b --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/Upgrade.cshtml @@ -0,0 +1,109 @@ +@using Resgrid.Model +@model Resgrid.Web.Areas.User.Models.Subscription.BuyNowView +@inject IStringLocalizer localizer +@{ + ViewBag.Title = "Resgrid | " + @localizer["UpgradePlanHeader"]; +} + + +
+
+

@localizer["UpgradePlanHeader"]

+ +
+
+ +
+
+
+
+
+
+
+
+

+ @localizer["BuyingFollowingPlan"] +

+ @if (Model.Plan.Frequency == (int)PlanFrequency.Yearly) + { +

+ @Model.Plan.Name for $@Model.Plan.Cost/@localizer["Year"] +

+ } + else + { +

+ @Model.Plan.Name for $@Model.Plan.Cost/@localizer["Month"] +

+ } +
+
+
+
+

+ @localizer["YourCurrentPlan"] +

+ @if (Model.Payment.Plan.Frequency == (int)PlanFrequency.Yearly) + { +

+ @Model.Payment.Plan.Name for $@Model.Payment.Plan.Cost/@localizer["Year"] +

+ } + else + { +

+ @Model.Payment.Plan.Name for $@Model.Payment.Plan.Cost/@localizer["Month"] +

+ } +
+
+
+
+ @localizer["ChangePlanBody"] +
+
+ + + +
+
+ @if (!String.IsNullOrEmpty(Model.Message)) + { +
+ @Model.Message +
+ } + @Html.AntiForgeryToken() + @Html.HiddenFor(m => m.PlanId) +
+
+
+ + +
+
+ @commonLocalizer["Cancel"] + +
+
+
+
+
+
+
+
+ +@section Scripts + { + +} diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Subscription/ViewInvoice.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/ViewInvoice.cshtml new file mode 100644 index 00000000..d497a5c7 --- /dev/null +++ b/Web/Resgrid.WebCore/Areas/User/Views/Subscription/ViewInvoice.cshtml @@ -0,0 +1,201 @@ +@using Resgrid.Framework +@using Resgrid.Model +@model Resgrid.Web.Areas.User.Models.Subscription.ViewInvoiceView +@inject IStringLocalizer localizer +@{ + Layout = null; +} + + + + + + Resgrid Invoice + + + + + + + + + + +
+
+
+ +
+ Resgrid, LLC.
+ 1802 North Carson Street
+ Suite 157
+ Carson City, NV 89701
+
+
+
+   +
+
+ + + + + + + + + + + + @if (Model.Charge != null) + { + + + + + + + + + } + + + + + + + + + +
@localizer["Invoice"] #@Model.Payment.PaymentId
@localizer["Processor"]@(((PaymentMethods)Model.Payment.Method).ToString())
@localizer["Customer"] #@Model.Charge.CustomerId
@localizer["Transaction"] #@Model.Charge.Id
@localizer["Department"] #@Model.Payment.DepartmentId
@localizer["InvoiceDate"]@Model.Payment.PurchaseOn.ToString("d")
+
+ +
+
+
+

@localizer["Invoice"]

+
+
+
+
+

@localizer["Customer"]: @Model.Payment.Department.Name

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
@localizer["Description"]@localizer["Period"]@localizer["Amount"]
@Model.Payment.Description@Model.Payment.EffectiveOn.ToString("d") to @Model.Payment.EndingOn.ToString("d")@Model.Payment.Amount.ToString("C", Cultures.UnitedStates)
 @localizer["Total"]@Model.Payment.Amount.ToString("C", Cultures.UnitedStates)
+
+
+
+
+   +
+
+
@localizer["Thanks"]
+
+
+
+
+   +
+
+ Phone: 1.888.570.4953 +
+
+ Email: team@resgrid.com +
+
+ Website: https://resgrid.com +
+
+
+ + + + + + + + + diff --git a/Web/Resgrid.WebCore/Resgrid.WebCore.csproj b/Web/Resgrid.WebCore/Resgrid.WebCore.csproj index 56479042..41654deb 100644 --- a/Web/Resgrid.WebCore/Resgrid.WebCore.csproj +++ b/Web/Resgrid.WebCore/Resgrid.WebCore.csproj @@ -110,6 +110,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Web/Resgrid.WebCore/Startup.cs b/Web/Resgrid.WebCore/Startup.cs index c66be8a4..03fec74f 100644 --- a/Web/Resgrid.WebCore/Startup.cs +++ b/Web/Resgrid.WebCore/Startup.cs @@ -333,7 +333,7 @@ public void ConfigureServices(IServiceCollection services) // Internal app js bundle pipeline.AddJavaScriptBundle("/js/int-bundle.js", "lib/metisMenu/dist/metisMenu.min.js", "lib/slimScroll/jquery.slimscroll.js", "lib/pace/pace.js", "lib/select2/dist/js/select2.full.js", "clib/kendo/js/kendo.web.min.js", "lib/bootstrap-tour/build/js/bootstrap-tour.min.js", "lib/toastr/toastr.min.js", - "clib/markerwithlabel/markerwithlabel.js", "clib/ujs/jquery-ujs.js", "lib/jquery-validate/dist/jquery.validate.min.js", "lib/jqueryui/jquery-ui.min.js", + /*"clib/markerwithlabel/markerwithlabel.js",*/ "clib/ujs/jquery-ujs.js", "lib/jquery-validate/dist/jquery.validate.min.js", "lib/jqueryui/jquery-ui.min.js", "lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js", "lib/signalr/dist/browser/signalr.js", "clib/picEdit/js/picedit.min.js", "lib/sweetalert/dist/sweetalert.min.js", "clib/bootstrap-wizard/bootstrap-wizard.min.js", "lib/quill/dist/quill.min.js", "lib/moment/min/moment.min.js", "lib/fullcalendar/dist/fullcalendar.min.js", "lib/leaflet/dist/leaflet.js", "lib/bstreeview/dist/js/bstreeview.min.js", diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/1.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/1.png new file mode 100644 index 00000000..ace11c47 Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/1.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/2.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/2.png new file mode 100644 index 00000000..ecb1fbea Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/2.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/3.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/3.png new file mode 100644 index 00000000..3b8dcdc8 Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/3.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/4.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/4.png new file mode 100644 index 00000000..11fac9a8 Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/4.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/5.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/5.png new file mode 100644 index 00000000..a00521e9 Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/5.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/6.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/6.png new file mode 100644 index 00000000..8fd22a39 Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/6.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/7.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/7.png new file mode 100644 index 00000000..3e1f57fd Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/7.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/congruent_pentagon.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/congruent_pentagon.png new file mode 100644 index 00000000..c7126603 Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/congruent_pentagon.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-1.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-1.png new file mode 100644 index 00000000..41c5c089 Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-1.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-2.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-2.png new file mode 100644 index 00000000..df46d46e Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-2.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-3.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-3.png new file mode 100644 index 00000000..7a80132d Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile-skin-3.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile.png new file mode 100644 index 00000000..7dea7f2c Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/header-profile.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/otis_redding.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/otis_redding.png new file mode 100644 index 00000000..7fa3533a Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/otis_redding.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/shattered.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/shattered.png new file mode 100644 index 00000000..90ed42b8 Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/shattered.png differ diff --git a/Web/Resgrid.WebCore/wwwroot/css/patterns/triangular.png b/Web/Resgrid.WebCore/wwwroot/css/patterns/triangular.png new file mode 100644 index 00000000..7f41795c Binary files /dev/null and b/Web/Resgrid.WebCore/wwwroot/css/patterns/triangular.png differ