Ticket Tailor: Require full name

This commit is contained in:
Kukks
2023-03-20 12:19:04 +01:00
parent b8b94abf02
commit f11b7823c0
8 changed files with 70 additions and 33 deletions

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>TicketTailor</Product> <Product>TicketTailor</Product>
<Description>Allows you to integrate with TicketTailor.com to sell tickets for Bitcoin</Description> <Description>Allows you to integrate with TicketTailor.com to sell tickets for Bitcoin</Description>
<Version>1.0.8</Version> <Version>1.0.9</Version>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->
<PropertyGroup> <PropertyGroup>

View File

@@ -53,6 +53,7 @@ namespace BTCPayServer.Plugins.TicketTailor
public async Task<IActionResult> Purchase(string storeId, TicketTailorViewModel request) public async Task<IActionResult> Purchase(string storeId, TicketTailorViewModel request)
{ {
var config = await _ticketTailorService.GetTicketTailorForStore(storeId); var config = await _ticketTailorService.GetTicketTailorForStore(storeId);
(TicketTailorClient.Hold, string error)? hold = null;
try try
{ {
if (config?.ApiKey is not null && config?.EventId is not null) if (config?.ApiKey is not null && config?.EventId is not null)
@@ -109,18 +110,18 @@ namespace BTCPayServer.Plugins.TicketTailor
price += ticketCost * purchaseRequestItem.Quantity; price += ticketCost * purchaseRequestItem.Quantity;
} }
var hold = await client.CreateHold(new TicketTailorClient.CreateHoldRequest() hold = await client.CreateHold(new TicketTailorClient.CreateHoldRequest()
{ {
EventId = evt.Id, EventId = evt.Id,
Note = "Created by BTCPay Server", Note = "Created by BTCPay Server",
TicketTypeId = request.Items.ToDictionary(item => item.TicketTypeId, item => item.Quantity) TicketTypeId = request.Items.ToDictionary(item => item.TicketTypeId, item => item.Quantity)
}); });
if (!string.IsNullOrEmpty(hold.error)) if (!string.IsNullOrEmpty(hold.Value.error))
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Html = $"Could not reserve tickets because {hold.error}" Html = $"Could not reserve tickets because {hold.Value.error}"
}); });
return RedirectToAction("View", new {storeId}); return RedirectToAction("View", new {storeId});
} }
@@ -130,23 +131,31 @@ namespace BTCPayServer.Plugins.TicketTailor
var redirectUrl = Request.GetAbsoluteUri(Url.Action("Receipt", var redirectUrl = Request.GetAbsoluteUri(Url.Action("Receipt",
"TicketTailor", new {storeId, invoiceId = "kukkskukkskukks"})); "TicketTailor", new {storeId, invoiceId = "kukkskukkskukks"}));
redirectUrl = redirectUrl.Replace("kukkskukkskukks", "{InvoiceId}"); redirectUrl = redirectUrl.Replace("kukkskukkskukks", "{InvoiceId}");
if(string.IsNullOrEmpty(request.Name)) request.Name??=string.Empty;
var nameSplit = request.Name.Split(" ", StringSplitOptions.RemoveEmptyEntries);
if (config.RequireFullName && nameSplit.Length < 2)
{ {
request.Name = "Anonymous lizard"; TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Html = "Please enter your full name."
});
return RedirectToAction("View", new {storeId});
} }
var nameSplit = request.Name.Split(" ", StringSplitOptions.RemoveEmptyEntries); request.Name = nameSplit.Length switch
if (nameSplit.Length < 2)
{ {
request.Name = nameSplit[0] + " Lizard"; 0 => "Satoshi Nakamoto",
} < 2 => $"{nameSplit} Nakamoto",
_ => request.Name
};
var inv = await btcpayClient.CreateInvoice(storeId, var inv = await btcpayClient.CreateInvoice(storeId,
new CreateInvoiceRequest() new CreateInvoiceRequest()
{ {
Amount = price, Amount = price,
Currency = evt.Currency, Currency = evt.Currency,
Type = InvoiceType.Standard, Type = InvoiceType.Standard,
AdditionalSearchTerms = new[] {"tickettailor", hold.Item1.Id, evt.Id}, AdditionalSearchTerms = new[] {"tickettailor", hold.Value.Item1.Id, evt.Id},
Checkout = Checkout =
{ {
RequiresRefundEmail = true, RequiresRefundEmail = true,
@@ -162,7 +171,7 @@ namespace BTCPayServer.Plugins.TicketTailor
btcpayUrl = Request.GetAbsoluteRoot(), btcpayUrl = Request.GetAbsoluteRoot(),
buyerName = request.Name, buyerName = request.Name,
buyerEmail = request.Email, buyerEmail = request.Email,
holdId = hold.Item1.Id, holdId = hold.Value.Item1.Id,
orderId="tickettailor" orderId="tickettailor"
}) })
}); });
@@ -173,14 +182,24 @@ namespace BTCPayServer.Plugins.TicketTailor
inv = await btcpayClient.GetInvoice(inv.StoreId, inv.Id); inv = await btcpayClient.GetInvoice(inv.StoreId, inv.Id);
} }
if (inv.Status == InvoiceStatus.Settled) return inv.Status == InvoiceStatus.Settled
return RedirectToAction("Receipt", new {storeId, invoiceId = inv.Id}); ? RedirectToAction("Receipt", new {storeId, invoiceId = inv.Id})
: RedirectToAction("Checkout", "UIInvoice", new {invoiceId = inv.Id});
return RedirectToAction("Checkout","UIInvoice", new {invoiceId = inv.Id});
} }
} }
catch (Exception e) catch (Exception e)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Html = $"Could not continue with ticket purchase.</br>{e.Message}"
});
if (hold?.Item1 is not null)
{
var client = new TicketTailorClient(_httpClientFactory, config.ApiKey);
await client.DeleteHold(hold?.Item1.Id);
}
} }
return RedirectToAction("View", new {storeId}); return RedirectToAction("View", new {storeId});
@@ -280,6 +299,7 @@ namespace BTCPayServer.Plugins.TicketTailor
vm.ShowDescription = TicketTailor.ShowDescription; vm.ShowDescription = TicketTailor.ShowDescription;
vm.BypassAvailabilityCheck = TicketTailor.BypassAvailabilityCheck; vm.BypassAvailabilityCheck = TicketTailor.BypassAvailabilityCheck;
vm.CustomCSS = TicketTailor.CustomCSS; vm.CustomCSS = TicketTailor.CustomCSS;
vm.RequireFullName = TicketTailor.RequireFullName;
vm.SpecificTickets = TicketTailor.SpecificTickets; vm.SpecificTickets = TicketTailor.SpecificTickets;
} }
} }
@@ -374,7 +394,8 @@ namespace BTCPayServer.Plugins.TicketTailor
ShowDescription = vm.ShowDescription, ShowDescription = vm.ShowDescription,
CustomCSS = vm.CustomCSS, CustomCSS = vm.CustomCSS,
SpecificTickets = vm.SpecificTickets, SpecificTickets = vm.SpecificTickets,
BypassAvailabilityCheck = vm.BypassAvailabilityCheck BypassAvailabilityCheck = vm.BypassAvailabilityCheck,
RequireFullName = vm.RequireFullName
}; };

View File

@@ -111,14 +111,23 @@ public class TicketTailorService : EventHostedServiceBase
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken) protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
{ {
if (evt is InvoiceEvent invoiceEvent) switch (evt)
{ {
if (invoiceEvent.Invoice.Metadata.OrderId != "tickettailor" || !new []{InvoiceStatus.Settled, InvoiceStatus.Expired, InvoiceStatus.Invalid}.Contains(invoiceEvent.Invoice.GetInvoiceState().Status.ToModernStatus())) case InvoiceEvent invoiceEvent when invoiceEvent.Invoice.Metadata.OrderId != "tickettailor" || !new []{InvoiceStatus.Settled, InvoiceStatus.Expired, InvoiceStatus.Invalid}.Contains(invoiceEvent.Invoice.GetInvoiceState().Status.ToModernStatus()):
{
return; return;
} case InvoiceEvent invoiceEvent:
if(_memoryCache.TryGetValue($"{nameof(TicketTailorService)}_{invoiceEvent.Invoice.Id}_issue_check_from_ui", out _))return;
await _memoryCache.GetOrCreateAsync(
$"{nameof(TicketTailorService)}_{invoiceEvent.Invoice.Id}_issue_check_from_ui", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2);
return true;
});
evt = new IssueTicket() {Invoice = invoiceEvent.Invoice}; evt = new IssueTicket() {Invoice = invoiceEvent.Invoice};
break;
} }
if (evt is not IssueTicket issueTicket) if (evt is not IssueTicket issueTicket)

View File

@@ -11,5 +11,6 @@ namespace BTCPayServer.Plugins.TicketTailor
public string CustomCSS { get; set; } public string CustomCSS { get; set; }
public List<SpecificTicket> SpecificTickets { get; set; } public List<SpecificTicket> SpecificTickets { get; set; }
public bool BypassAvailabilityCheck { get; set; } public bool BypassAvailabilityCheck { get; set; }
public bool RequireFullName { get; set; }
} }
} }

View File

@@ -15,6 +15,7 @@ public class UpdateTicketTailorSettingsViewModel
public List<SpecificTicket> SpecificTickets { get; set; } public List<SpecificTicket> SpecificTickets { get; set; }
public bool BypassAvailabilityCheck { get; set; } public bool BypassAvailabilityCheck { get; set; }
public bool RequireFullName { get; set; }
} }
public class SpecificTicket public class SpecificTicket

View File

@@ -54,6 +54,11 @@
<label asp-for="BypassAvailabilityCheck" class="form-check-label">Bypass availability checks of TicketTailor (such as when in draft mode)</label> <label asp-for="BypassAvailabilityCheck" class="form-check-label">Bypass availability checks of TicketTailor (such as when in draft mode)</label>
<span asp-validation-for="BypassAvailabilityCheck" class="text-danger"></span> <span asp-validation-for="BypassAvailabilityCheck" class="text-danger"></span>
</div> </div>
<div class="form-group form-check">
<input asp-for="RequireFullName" type="checkbox" class="form-check-input"/>
<label asp-for="RequireFullName" class="form-check-label">Require full name to be provided (if unchecked and not provided, the gap will be filled in with a placeholder)</label>
<span asp-validation-for="RequireFullName" class="text-danger"></span>
</div>
<div class="form-group"> <div class="form-group">
<label asp-for="CustomCSS" class="form-label">Additional Custom CSS</label> <label asp-for="CustomCSS" class="form-label">Additional Custom CSS</label>

View File

@@ -1,15 +1,9 @@
@using Microsoft.AspNetCore.Routing @using Microsoft.AspNetCore.Routing
@using BTCPayServer.Plugins.TicketTailor @using BTCPayServer.Plugins.TicketTailor
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@using NBitcoin
@model BTCPayServer.Plugins.TicketTailor.TicketTailorViewModel @model BTCPayServer.Plugins.TicketTailor.TicketTailorViewModel
@inject BTCPayServer.Security.ContentSecurityPolicies csp;
@{ @{
var nonce = RandomUtils.GetUInt256().ToString().Substring(0, 32);
csp.Add("script-src", $"'nonce-{nonce}'");
csp.AllowUnsafeHashes();
Layout = "_LayoutSimple"; Layout = "_LayoutSimple";
var available = Model.Settings.BypassAvailabilityCheck || (Model.Event.Unavailable != "true" && Model.Event.TicketsAvailable == "true"); var available = Model.Settings.BypassAvailabilityCheck || (Model.Event.Unavailable != "true" && Model.Event.TicketsAvailable == "true");
Model.Settings.SpecificTickets ??= new List<SpecificTicket>(); Model.Settings.SpecificTickets ??= new List<SpecificTicket>();
@@ -29,7 +23,7 @@ footer {
} }
</style> </style>
<script nonce="@nonce"> <script>
document.addEventListener("DOMContentLoaded", ()=>{ document.addEventListener("DOMContentLoaded", ()=>{
const btn = document.querySelector("button[type='submit']"); const btn = document.querySelector("button[type='submit']");
document.querySelectorAll("input").forEach(value => value.addEventListener("input", (evt)=>{ document.querySelectorAll("input").forEach(value => value.addEventListener("input", (evt)=>{
@@ -77,7 +71,8 @@ document.addEventListener("DOMContentLoaded", ()=>{
<div class="row g-2 justify-content-center mb-4" id="ticket-form-container"> <div class="row g-2 justify-content-center mb-4" id="ticket-form-container">
<div class="col-sm-6 col-md-4"> <div class="col-sm-6 col-md-4">
<div class="form-floating"> <div class="form-floating">
<input type="text" minlength="3" asp-for="Name" class="form-control"> <input type="text" asp-for="Name" class="form-control" required="@Model.Settings.RequireFullName"
pattern="^(\w\w+)\s(\w+)$" title="Please enter your first and last name, separated with a space.">
<label >Name</label> <label >Name</label>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,9 @@
@using BTCPayServer.Abstractions.Services @using BTCPayServer.Abstractions.Extensions
@inject BTCPayServer.Abstractions.Services.Safe Safe
@inject Safe Safe @addTagHelper *, BTCPayServer.Abstractions
@addTagHelper *, BTCPayServer.TagHelpers
@addTagHelper *, BTCPayServer.Views.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, BTCPayServer