using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PlotLine.Data; using PlotLine.Models; using PlotLine.Services; using PlotLine.ViewModels; namespace PlotLine.Controllers; [AllowAnonymous] public sealed class PricingController( ICurrentUserService currentUser, IUserRepository users, ISubscriptionService subscriptions, IStripeBillingService stripeBilling, IConfiguration configuration) : Controller { [HttpGet("pricing", Name = PublicRouteNames.Pricing)] public async Task Index() { const string description = "Compare PlotDirector plans and choose the right subscription for organising your novel, series, characters, plot threads, timelines, and story details."; PublicSeo.Apply(ViewData, configuration, new SeoMetadata { Title = "Pricing", Description = description, Keywords = "PlotDirector pricing, novel planning software pricing, writing software plans, author tools subscription", CanonicalPath = "/pricing", JsonLd = [ PublicSeo.WebPageJsonLd(configuration, "WebPage", "Pricing", "/pricing", description), PublicSeo.JsonLd(new Dictionary { ["@context"] = "https://schema.org", ["@type"] = "FAQPage", ["mainEntity"] = new[] { new Dictionary { ["@type"] = "Question", ["name"] = "Can I start with a free trial?", ["acceptedAnswer"] = new Dictionary { ["@type"] = "Answer", ["text"] = "Yes. Creating an account starts a 30-day Draft Desk trial immediately." } }, new Dictionary { ["@type"] = "Question", ["name"] = "Do I need a credit card for the trial?", ["acceptedAnswer"] = new Dictionary { ["@type"] = "Answer", ["text"] = "No. Payment details are not required for the free trial." } } } }) ] }); UserSubscriptionDetail? currentSubscription = null; if (currentUser.UserId is int userId) { currentSubscription = await subscriptions.GetCurrentSubscriptionAsync(userId); } return View(new PricingViewModel { Plans = await subscriptions.GetActiveSubscriptionLevelsAsync(), CurrentSubscriptionLevelID = currentSubscription?.SubscriptionLevelID, IsSignedIn = currentUser.UserId.HasValue }); } [HttpGet] public IActionResult StartTrial() { if (!currentUser.UserId.HasValue) { return RedirectToAction("Register", "Account"); } return RedirectToAction("Subscription", "ManageAccount"); } [HttpGet] public async Task Checkout(int id, string billingInterval = "Monthly") { if (!currentUser.UserId.HasValue) { var returnUrl = Url.Action(nameof(Checkout), "Pricing", new { id, billingInterval }) ?? "/pricing"; return RedirectToAction("Login", "Account", new { returnUrl }); } var user = await users.GetByIdAsync(currentUser.UserId.Value); if (user is null) { return RedirectToAction("Login", "Account"); } var request = new StripeCheckoutRequest { SubscriptionLevelID = id, BillingInterval = string.Equals(billingInterval, "Annual", StringComparison.OrdinalIgnoreCase) ? "Annual" : "Monthly" }; var successUrl = Url.Action("SubscriptionSuccess", "ManageAccount", null, Request.Scheme) ?? string.Empty; var cancelUrl = Url.Action(nameof(Index), "Pricing", null, Request.Scheme) ?? string.Empty; var result = await stripeBilling.StartCheckoutOrChangePlanAsync(user, request, successUrl, cancelUrl); if (result.Succeeded && !string.IsNullOrWhiteSpace(result.RedirectUrl)) { return Redirect(result.RedirectUrl); } TempData["PricingMessage"] = result.Message; return RedirectToAction(nameof(Index)); } }