mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 05:54:26 +01:00
139 lines
5.4 KiB
C#
139 lines
5.4 KiB
C#
#nullable enable
|
|
|
|
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using BTCPayServer.Abstractions.Extensions;
|
|
using BTCPayServer.Abstractions.Models;
|
|
using BTCPayServer.Client.Models;
|
|
using BTCPayServer.Data;
|
|
using BTCPayServer.Data.Subscriptions;
|
|
using BTCPayServer.Models;
|
|
using BTCPayServer.Services;
|
|
using BTCPayServer.Services.Invoices;
|
|
using BTCPayServer.Views.UIStoreMembership;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using Microsoft.Extensions.Localization;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace BTCPayServer.Plugins.Subscriptions.Controllers;
|
|
|
|
|
|
[AllowAnonymous]
|
|
[AutoValidateAntiforgeryToken]
|
|
[Area(SubscriptionsPlugin.Area)]
|
|
[Route("plan-checkout/{checkoutId}")]
|
|
public class UIPlanCheckoutController(
|
|
ApplicationDbContextFactory dbContextFactory,
|
|
LinkGenerator linkGenerator,
|
|
UriResolver uriResolver,
|
|
IStringLocalizer stringLocalizer,
|
|
SubscriptionHostedService subsService)
|
|
: UISubscriptionControllerBase(dbContextFactory, linkGenerator, stringLocalizer, subsService)
|
|
{
|
|
[HttpGet]
|
|
public async Task<IActionResult> PlanCheckout(string checkoutId)
|
|
{
|
|
await using var ctx = DbContextFactory.CreateContext();
|
|
var checkout = await ctx.PlanCheckouts.GetCheckout(checkoutId);
|
|
var plan = checkout?.Plan;
|
|
if (plan is null || checkout is null)
|
|
return NotFound();
|
|
var prefilledEmail = GetInvoiceMetadata(checkout).BuyerEmail;
|
|
if (checkout.Subscriber is not null)
|
|
prefilledEmail = checkout.Subscriber.Customer.Email.Get();
|
|
var vm = new PlanCheckoutViewModel()
|
|
{
|
|
Id = plan.Id,
|
|
StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, uriResolver, plan.Offering.App.StoreData.GetStoreBlob()),
|
|
StoreName = plan.Offering.App.StoreData.StoreName,
|
|
Title = plan.Name,
|
|
Data = plan,
|
|
Email = prefilledEmail,
|
|
IsPrefilled = prefilledEmail?.IsValidEmail() is true,
|
|
IsTrial = checkout.IsTrial
|
|
};
|
|
return View(vm);
|
|
}
|
|
|
|
private static InvoiceMetadata GetInvoiceMetadata(PlanCheckoutData checkout)
|
|
{
|
|
return InvoiceMetadata.FromJObject(JObject.Parse(checkout.InvoiceMetadata));
|
|
}
|
|
|
|
[HttpGet("plan-checkout/default-redirect")]
|
|
public async Task<IActionResult> PlanCheckoutDefaultRedirect(string? checkoutPlanId = null)
|
|
{
|
|
if (checkoutPlanId is null)
|
|
return NotFound();
|
|
await using var ctx = DbContextFactory.CreateContext();
|
|
var checkout = await ctx.PlanCheckouts.GetCheckout(checkoutPlanId);
|
|
if (checkout is null)
|
|
return NotFound();
|
|
|
|
return View(new PlanCheckoutDefaultRedirectViewModel(checkout)
|
|
{
|
|
StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, uriResolver, checkout.Plan.Offering.App.StoreData.GetStoreBlob()),
|
|
StoreName = checkout.Plan.Offering.App.StoreData.StoreName,
|
|
});
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<IActionResult> PlanCheckout(string checkoutId, PlanCheckoutViewModel vm,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await using var ctx = DbContextFactory.CreateContext();
|
|
var checkout = await ctx.PlanCheckouts.GetCheckout(checkoutId);
|
|
if (checkout is null)
|
|
return NotFound();
|
|
var checkoutInvoice = checkout.InvoiceId is null ? null : await ctx.Invoices.FindAsync([checkout.InvoiceId], cancellationToken);
|
|
if (checkoutInvoice is not null)
|
|
{
|
|
var status = checkoutInvoice.GetInvoiceState().Status;
|
|
|
|
if (status is InvoiceStatus.Settled && checkout.GetRedirectUrl() is string url)
|
|
return Redirect(url);
|
|
if (status is not (InvoiceStatus.Expired or InvoiceStatus.Invalid))
|
|
return RedirectToInvoiceCheckout(checkoutInvoice.Id);
|
|
}
|
|
|
|
var subscriber = checkout.Subscriber;
|
|
CustomerSelector customerSelector;
|
|
if (subscriber is null)
|
|
{
|
|
var invoiceMetadata = GetInvoiceMetadata(checkout);
|
|
if (invoiceMetadata.BuyerEmail is not null)
|
|
vm.Email = invoiceMetadata.BuyerEmail;
|
|
customerSelector = CustomerSelector.ByEmail(vm.Email);
|
|
if (!vm.Email.IsValidEmail())
|
|
ModelState.AddModelError(nameof(vm.Email), "Invalid email format");
|
|
if (!ModelState.IsValid)
|
|
return await PlanCheckout(checkoutId);
|
|
if (checkout.NewSubscriber)
|
|
{
|
|
var sub = await ctx.Subscribers.GetBySelector(checkout.Plan.OfferingId, customerSelector);
|
|
if (sub is not null)
|
|
{
|
|
ModelState.AddModelError(nameof(vm.Email), "This email already has a subscription to this offering");
|
|
return await PlanCheckout(checkoutId);
|
|
}
|
|
}
|
|
|
|
if (invoiceMetadata.BuyerEmail is null)
|
|
{
|
|
invoiceMetadata.BuyerEmail = vm.Email;
|
|
checkout.InvoiceMetadata = invoiceMetadata.ToJObject().ToString();
|
|
await ctx.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
customerSelector = subscriber.CustomerSelector;
|
|
ModelState.Remove(nameof(vm.Email));
|
|
}
|
|
return await RedirectToPlanCheckoutPayment(checkoutId, customerSelector, cancellationToken);
|
|
}
|
|
}
|