update tt

This commit is contained in:
Kukks
2023-04-24 16:14:25 +02:00
parent 6617b13941
commit b06e5ca95c
7 changed files with 140 additions and 12 deletions

View File

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

View File

@@ -335,4 +335,44 @@ public class TicketTailorClient : IDisposable
[JsonPropertyName("venue")] public Venue Venue { get; set; }
}
public async Task<DiscountCode?> GetDiscountCode(string code)
{
var response = await _httpClient.GetAsync($"/v1/discounts?code={code}");
if (!response.IsSuccessStatusCode)
{
return null;
}
var result = await response.Content.ReadFromJsonAsync<DataHolder<DiscountCode[]>>();
return result?.Data?.FirstOrDefault();
}
public class DiscountCode
{
public string id { get; set; }
public string code { get; set; }
public Expires? expires { get; set; }
public int? max_redemptions { get; set; }
public string name { get; set; }
public int? face_value_amount { get; set; }
public int? face_value_percentage { get; set; }
public string[] ticket_types { get; set; }
public int? times_redeemed { get; set; }
public string type { get; set; }
public class Expires
{
public string date { get; set; }
public string formatted { get; set; }
public string iso { get; set; }
public string time { get; set; }
public string timezone { get; set; }
public long unix { get; set; }
}
}
}

View File

@@ -50,7 +50,7 @@ namespace BTCPayServer.Plugins.TicketTailor
[AllowAnonymous]
[HttpPost("")]
public async Task<IActionResult> Purchase(string storeId, TicketTailorViewModel request)
public async Task<IActionResult> Purchase(string storeId, TicketTailorViewModel request, bool preview = false)
{
var config = await _ticketTailorService.GetTicketTailorForStore(storeId);
(TicketTailorClient.Hold, string error)? hold = null;
@@ -66,6 +66,17 @@ namespace BTCPayServer.Plugins.TicketTailor
}
var price = 0m;
TicketTailorClient.DiscountCode discountCode = null;
if (!string.IsNullOrEmpty(request.DiscountCode) && config.AllowDiscountCodes)
{
discountCode = await client.GetDiscountCode(request.DiscountCode);
if (discountCode?.expires?.unix is not null && DateTimeOffset.FromUnixTimeSeconds(discountCode.expires.unix) < DateTimeOffset.Now)
{
discountCode = null;
}
}
var discountedAmount = 0m;
foreach (var purchaseRequestItem in request.Items)
{
if (purchaseRequestItem.Quantity <= 0)
@@ -107,7 +118,36 @@ namespace BTCPayServer.Plugins.TicketTailor
ticketCost =specificTicket.Price.GetValueOrDefault(ticketType.Price);
}
price += ticketCost * purchaseRequestItem.Quantity;
var originalTicketCost = ticketCost;
if (discountCode?.ticket_types.Contains(ticketType?.Id) is true)
{
switch (discountCode.type)
{
case "fixed_amount" when discountCode.face_value_amount is not null:
ticketCost -= (discountCode.face_value_amount.Value / 100m);
break;
case "percentage" when discountCode.face_value_percentage is not null:
var percentageOff = (discountCode.face_value_percentage.Value / 100m);
ticketCost -= (ticketCost * percentageOff);
break;
}
}
var thisTicketBatchCost = ticketCost * purchaseRequestItem.Quantity;
discountedAmount += (originalTicketCost * purchaseRequestItem.Quantity) - thisTicketBatchCost;
price += thisTicketBatchCost;
}
if (preview)
{
return Json(new
{
discountedAmount,
total = price
});
}
hold = await client.CreateHold(new TicketTailorClient.CreateHoldRequest()
@@ -172,7 +212,9 @@ namespace BTCPayServer.Plugins.TicketTailor
buyerName = request.Name,
buyerEmail = request.Email,
holdId = hold.Value.Item1.Id,
orderId="tickettailor"
orderId="tickettailor",
discountCode,
discountedAmount
})
});
@@ -300,6 +342,7 @@ namespace BTCPayServer.Plugins.TicketTailor
vm.BypassAvailabilityCheck = TicketTailor.BypassAvailabilityCheck;
vm.CustomCSS = TicketTailor.CustomCSS;
vm.RequireFullName = TicketTailor.RequireFullName;
vm.AllowDiscountCodes = TicketTailor.AllowDiscountCodes;
vm.SpecificTickets = TicketTailor.SpecificTickets;
}
}
@@ -395,7 +438,8 @@ namespace BTCPayServer.Plugins.TicketTailor
CustomCSS = vm.CustomCSS,
SpecificTickets = vm.SpecificTickets,
BypassAvailabilityCheck = vm.BypassAvailabilityCheck,
RequireFullName = vm.RequireFullName
RequireFullName = vm.RequireFullName,
AllowDiscountCodes = vm.AllowDiscountCodes
};

View File

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

View File

@@ -16,6 +16,7 @@ public class UpdateTicketTailorSettingsViewModel
public List<SpecificTicket> SpecificTickets { get; set; }
public bool BypassAvailabilityCheck { get; set; }
public bool RequireFullName { get; set; }
public bool AllowDiscountCodes { get; set; }
}
public class SpecificTicket
@@ -37,6 +38,7 @@ public class TicketTailorViewModel
public PurchaseRequestItem[] Items { get; set; }
public string AccessCode { get; set; }
public string DiscountCode { get; set; }
public class PurchaseRequestItem
{

View File

@@ -59,6 +59,11 @@
<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 form-check">
<input asp-for="AllowDiscountCodes" type="checkbox" class="form-check-input"/>
<label asp-for="AllowDiscountCodes" class="form-check-label">Allow discount codes (due to the nature of Ticket Tailor's API, redemption limit is not applied.</label>
<span asp-validation-for="AllowDiscountCodes" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CustomCSS" class="form-label">Additional Custom CSS</label>

View File

@@ -3,10 +3,15 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Plugins.TicketTailor.TicketTailorViewModel
@{
var storeId = Context.GetRouteValue("storeId");
Layout = "_LayoutSimple";
var available = Model.Settings.BypassAvailabilityCheck || (Model.Event.Unavailable != "true" && Model.Event.TicketsAvailable == "true");
Model.Settings.SpecificTickets ??= new List<SpecificTicket>();
Context.Request.Query.TryGetValue("accessCode", out var accessCode);
if (Context.Request.Query.TryGetValue("discount", out var discount))
{
Model.DiscountCode = discount;
}
}
<style>
hr:last-child{
@@ -24,10 +29,13 @@ footer {
</style>
<script>
document.addEventListener("DOMContentLoaded", ()=>{
const form = document.querySelector("form");
const btn = document.querySelector("button[type='submit']");
const inputs = document.querySelectorAll("input");
const discountCode = document.querySelector("#DiscountCode");
inputs.forEach(value => value.addEventListener("input", (evt)=>{
let total = 0;
let totalQty = 0;
document.querySelectorAll("[data-price]").forEach(value1 => {
@@ -48,8 +56,25 @@ document.addEventListener("DOMContentLoaded", ()=>{
btn.setAttribute("disabled", "disabled");
}
btn.textContent = `Purchase for ${total.toFixed(2)} @Model.Event.Currency.ToUpperInvariant()`
if (discountCode && discountCode.value && totalQty > 0){
const data = new FormData(form);
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const response = JSON.parse(this.responseText);
if (response.discountedAmount){
btn.innerHTML = `Purchase for ${response.total.toFixed(2)} @Model.Event.Currency.ToUpperInvariant()<br/><span class="">${response.discountedAmount.toFixed(2)} @Model.Event.Currency.ToUpperInvariant() discount</span>`
} else{
btn.textContent = `Purchase for ${response.total.toFixed(2)} @Model.Event.Currency.ToUpperInvariant()`
}
}
}
xhttp.open("POST", "@Url.Action("Purchase", new {storeId, preview = true})", true);
xhttp.send(data);
}
}))
document.querySelector("form").addEventListener("submit", ()=>{
form.addEventListener("submit", ()=>{
btn.setAttribute("disabled", "disabled");
inputs.forEach(value => value.setAttribute("readonly", "readonly"));
})
@@ -67,14 +92,14 @@ document.addEventListener("DOMContentLoaded", ()=>{
<div class="overflow-hidden col-12 ">@Safe.Raw(Model.Event.Description)</div>
</div>
}
<form method="post" asp-controller="TicketTailor" asp-action="Purchase" asp-antiforgery="false" asp-route-storeId="@Context.GetRouteValue("storeId")">
<form method="post" asp-controller="TicketTailor" asp-action="Purchase" asp-antiforgery="false" asp-route-storeId="@storeId">
<input type="hidden" asp-for="AccessCode" value="@accessCode"/>
<div class="row g-2 justify-content-center mb-4" id="ticket-form-container">
<div class="col-sm-6 col-md-4">
<div class="form-floating">
<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 asp-for="Name">Full Name</label>
<label asp-for="Name">Full Name</label>
</div>
</div>
<div class="col-sm-6 col-md-4">
@@ -83,6 +108,8 @@ document.addEventListener("DOMContentLoaded", ()=>{
<label asp-for="Email">Email</label>
</div>
</div>
</div>
<div class="row g-2 justify-content-center mb-4">
<div class="col-sm-12 col-md-8">
@@ -138,7 +165,7 @@ document.addEventListener("DOMContentLoaded", ()=>{
var purchasable = available && (specific || new[] {"on_sale", "locked"}.Contains(item.Status)) && item.Quantity > 0;
<div class="row justify-content-between">
<div class="col-lg-8 col-sm-12">
<div class="col-lg-8 col-sm-12">
<h5 >@item.Name</h5>
<p>@Safe.Raw(item.Description)</p>
</div>
@@ -149,9 +176,9 @@ document.addEventListener("DOMContentLoaded", ()=>{
<div class="form-floating">
<input type="number"
class="form-control" asp-for="Items[index].Quantity" max="@item.MaxPerOrder"
min="0" data-price="@item.Price" />
min="0" data-price="@item.Price"/>
<label asp-for="Items[index].Quantity">Quantity</label>
<label asp-for="Items[index].Quantity">Quantity</label>
</div>
<span class="input-group-text">
@@ -178,6 +205,15 @@ document.addEventListener("DOMContentLoaded", ()=>{
</div>
@if (Model.Settings.AllowDiscountCodes)
{
<div class="col-sm-12 col-md-8 text-center">
<div class="form-floating">
<input required type="text" asp-for="DiscountCode" class="form-control"/>
<label asp-for="DiscountCode">Promo code</label>
</div>
</div>
}
<div class="col-sm-12 col-md-8 text-center">
<button class="btn btn-primary btn-lg m-auto" type="submit" disabled="disabled">Purchase</button>