mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
update tt
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -73,7 +84,7 @@ namespace BTCPayServer.Plugins.TicketTailor
|
||||
continue;
|
||||
}
|
||||
var ticketType = evt.TicketTypes.FirstOrDefault(type => type.Id == purchaseRequestItem.TicketTypeId);
|
||||
|
||||
|
||||
var specificTicket =
|
||||
config.SpecificTickets?.SingleOrDefault(ticket => ticketType?.Id == ticket.TicketTypeId);
|
||||
if ((config.SpecificTickets?.Any() is true && specificTicket is null) || ticketType is null ||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user