mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
upd tt
This commit is contained in:
@@ -11,6 +11,7 @@ using BTCPayServer.Client;
|
|||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -68,7 +69,7 @@ namespace BTCPayServer.Plugins.TicketTailor
|
|||||||
[HttpGet("plugins/TicketTailor/{appId}")]
|
[HttpGet("plugins/TicketTailor/{appId}")]
|
||||||
public async Task<IActionResult> View(string appId)
|
public async Task<IActionResult> View(string appId)
|
||||||
{
|
{
|
||||||
var app = await _appService.GetApp(appId, TicketTailorApp.AppType);
|
var app = await _appService.GetApp(appId, TicketTailorApp.AppType, true);
|
||||||
if (app is null)
|
if (app is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
try
|
try
|
||||||
@@ -83,7 +84,11 @@ namespace BTCPayServer.Plugins.TicketTailor
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return View(new TicketTailorViewModel() {Event = evt, Settings = config});
|
return View(new TicketTailorViewModel()
|
||||||
|
{
|
||||||
|
Event = evt, Settings = config,
|
||||||
|
StoreBranding = new StoreBrandingViewModel(app.StoreData.GetStoreBlob())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -380,6 +385,7 @@ namespace BTCPayServer.Plugins.TicketTailor
|
|||||||
vm.ApiKey = tt.ApiKey;
|
vm.ApiKey = tt.ApiKey;
|
||||||
vm.EventId = tt.EventId;
|
vm.EventId = tt.EventId;
|
||||||
vm.ShowDescription = tt.ShowDescription;
|
vm.ShowDescription = tt.ShowDescription;
|
||||||
|
vm.SendEmail = tt.SendEmail;
|
||||||
vm.BypassAvailabilityCheck = tt.BypassAvailabilityCheck;
|
vm.BypassAvailabilityCheck = tt.BypassAvailabilityCheck;
|
||||||
vm.CustomCSS = tt.CustomCSS;
|
vm.CustomCSS = tt.CustomCSS;
|
||||||
vm.RequireFullName = tt.RequireFullName;
|
vm.RequireFullName = tt.RequireFullName;
|
||||||
@@ -483,7 +489,8 @@ namespace BTCPayServer.Plugins.TicketTailor
|
|||||||
SpecificTickets = vm.SpecificTickets,
|
SpecificTickets = vm.SpecificTickets,
|
||||||
BypassAvailabilityCheck = vm.BypassAvailabilityCheck,
|
BypassAvailabilityCheck = vm.BypassAvailabilityCheck,
|
||||||
RequireFullName = vm.RequireFullName,
|
RequireFullName = vm.RequireFullName,
|
||||||
AllowDiscountCodes = vm.AllowDiscountCodes
|
AllowDiscountCodes = vm.AllowDiscountCodes,
|
||||||
|
SendEmail = vm.SendEmail
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,10 @@ public class TicketTailorService : EventHostedServiceBase
|
|||||||
}
|
}
|
||||||
await _invoiceRepository.UpdateInvoiceMetadata(invoice.Id, invoice.StoreId, invoice.Metadata.ToJObject());
|
await _invoiceRepository.UpdateInvoiceMetadata(invoice.Id, invoice.StoreId, invoice.Metadata.ToJObject());
|
||||||
await _invoiceRepository.AddInvoiceLogs(invoice.Id, invLogs);
|
await _invoiceRepository.AddInvoiceLogs(invoice.Id, invLogs);
|
||||||
|
|
||||||
|
if (settings.SendEmail)
|
||||||
|
{
|
||||||
|
|
||||||
var uri = new Uri(btcpayUrl);
|
var uri = new Uri(btcpayUrl);
|
||||||
var url =
|
var url =
|
||||||
_linkGenerator.GetUriByAction("Receipt",
|
_linkGenerator.GetUriByAction("Receipt",
|
||||||
@@ -257,6 +261,8 @@ public class TicketTailorService : EventHostedServiceBase
|
|||||||
{
|
{
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ namespace BTCPayServer.Plugins.TicketTailor
|
|||||||
public bool BypassAvailabilityCheck { get; set; }
|
public bool BypassAvailabilityCheck { get; set; }
|
||||||
public bool RequireFullName { get; set; }
|
public bool RequireFullName { get; set; }
|
||||||
public bool AllowDiscountCodes { get; set; }
|
public bool AllowDiscountCodes { get; set; }
|
||||||
|
public bool SendEmail { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using BTCPayServer.Models;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
namespace BTCPayServer.Plugins.TicketTailor;
|
namespace BTCPayServer.Plugins.TicketTailor;
|
||||||
@@ -12,6 +13,7 @@ public class UpdateTicketTailorSettingsViewModel
|
|||||||
public SelectList Events { get; set; }
|
public SelectList Events { get; set; }
|
||||||
public string EventId { get; set; }
|
public string EventId { get; set; }
|
||||||
public bool ShowDescription { get; set; }
|
public bool ShowDescription { get; set; }
|
||||||
|
public bool SendEmail { get; set; }
|
||||||
public string CustomCSS { get; set; }
|
public string CustomCSS { get; set; }
|
||||||
public TicketTailorClient.TicketType[] TicketTypes { get; set; }
|
public TicketTailorClient.TicketType[] TicketTypes { get; set; }
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ public class TicketTailorViewModel
|
|||||||
public PurchaseRequestItem[] Items { get; set; }
|
public PurchaseRequestItem[] Items { get; set; }
|
||||||
public string AccessCode { get; set; }
|
public string AccessCode { get; set; }
|
||||||
public string DiscountCode { get; set; }
|
public string DiscountCode { get; set; }
|
||||||
|
public StoreBrandingViewModel StoreBranding { get; set; }
|
||||||
|
|
||||||
public class PurchaseRequestItem
|
public class PurchaseRequestItem
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -89,6 +89,11 @@
|
|||||||
<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>
|
<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>
|
<span asp-validation-for="AllowDiscountCodes" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group form-check">
|
||||||
|
<input asp-for="SendEmail" type="checkbox" class="form-check-input"/>
|
||||||
|
<label asp-for="SendEmail" class="form-check-label">Send an email when an invoice settles and the ticket is issued. </label>
|
||||||
|
<span asp-validation-for="SendEmail" 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>
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
@model BTCPayServer.Plugins.TicketTailor.TicketTailorViewModel
|
@model BTCPayServer.Plugins.TicketTailor.TicketTailorViewModel
|
||||||
@{
|
@{
|
||||||
var appId = Context.GetRouteValue("appId");
|
var appId = Context.GetRouteValue("appId");
|
||||||
Layout = "_LayoutSimple";
|
ViewData["StoreBranding"] = Model.StoreBranding;
|
||||||
|
Layout = null;
|
||||||
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>();
|
||||||
Context.Request.Query.TryGetValue("accessCode", out var accessCode);
|
Context.Request.Query.TryGetValue("accessCode", out var accessCode);
|
||||||
@@ -13,85 +14,36 @@
|
|||||||
Model.DiscountCode = discount;
|
Model.DiscountCode = discount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<partial name="LayoutHead"/>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
hr:last-child{
|
hr:last-child{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
footer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(Model.Settings.CustomCSS))
|
@if (!string.IsNullOrEmpty(Model.Settings.CustomCSS))
|
||||||
{
|
{
|
||||||
@Safe.Raw(Model.Settings.CustomCSS)
|
@Safe.Raw(Model.Settings.CustomCSS)
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
</head>
|
||||||
document.addEventListener("DOMContentLoaded", ()=>{
|
<body class="min-vh-100">
|
||||||
const form = document.querySelector("form");
|
<div id="TicketTailor" class="public-page-wrap">
|
||||||
const btn = document.querySelector("button[type='submit']");
|
<partial name="_StoreHeader" model="(Model.Event.Title, Model.StoreBranding)"/>
|
||||||
const inputs = document.querySelectorAll("input");
|
<main>
|
||||||
const discountCode = document.querySelector("#DiscountCode");
|
|
||||||
inputs.forEach(value => value.addEventListener("input", (evt)=>{
|
|
||||||
|
|
||||||
|
|
||||||
let total = 0;
|
|
||||||
let totalQty = 0;
|
|
||||||
document.querySelectorAll("[data-price]").forEach(value1 => {
|
|
||||||
if (!!value1.value){
|
|
||||||
const qty = parseInt(value1.value);
|
|
||||||
if (qty > 0){
|
|
||||||
|
|
||||||
const price = parseFloat(value1.dataset.price).toPrecision(12);
|
|
||||||
total += price * qty;
|
|
||||||
totalQty += qty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (totalQty > 0){
|
|
||||||
btn.removeAttribute("disabled");
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
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 {appId, preview = true})", true);
|
|
||||||
xhttp.send(data);
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
form.addEventListener("submit", ()=>{
|
|
||||||
btn.setAttribute("disabled", "disabled");
|
|
||||||
inputs.forEach(value => value.setAttribute("readonly", "readonly"));
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<div class="container d-flex h-100">
|
|
||||||
<div class="justify-content-center mx-auto px-2 py-3 w-100 m-auto">
|
|
||||||
<partial name="_StatusMessage"/>
|
<partial name="_StatusMessage"/>
|
||||||
|
<div class="text-muted mb-4 text-center lead ">@Model.Event.Start.Formatted - @Model.Event.EventEnd.Formatted</div>
|
||||||
<h1 class="text-center ">@Model.Event.Title</h1>
|
|
||||||
<h2 class="text-muted mb-4 text-center ">@Model.Event.Start.Formatted - @Model.Event.EventEnd.Formatted</h2>
|
|
||||||
@if (Model.Settings.ShowDescription && !string.IsNullOrEmpty(Model.Event.Description))
|
@if (Model.Settings.ShowDescription && !string.IsNullOrEmpty(Model.Event.Description))
|
||||||
{
|
{
|
||||||
<div class="row" id="ticket-tailor-description text-center ">
|
<div class="ticket-tailor-description lead">@Safe.Raw(Model.Event.Description)</div>
|
||||||
<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-appId="@appId">
|
<form method="post" asp-controller="TicketTailor" asp-action="Purchase" asp-antiforgery="false" asp-route-appId="@appId">
|
||||||
<input type="hidden" asp-for="AccessCode" value="@accessCode"/>
|
<input type="hidden" asp-for="AccessCode" value="@accessCode"/>
|
||||||
<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">
|
||||||
@@ -122,7 +74,6 @@ document.addEventListener("DOMContentLoaded", ()=>{
|
|||||||
<div class="bg-tile w-100 p-4 mb-2">
|
<div class="bg-tile w-100 p-4 mb-2">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(groupedTickets.Key))
|
@if (!string.IsNullOrEmpty(groupedTickets.Key))
|
||||||
{
|
{
|
||||||
var group = Model.Event.TicketGroups.First(ticketGroup => ticketGroup.Id == groupedTickets.Key);
|
var group = Model.Event.TicketGroups.First(ticketGroup => ticketGroup.Id == groupedTickets.Key);
|
||||||
@@ -140,25 +91,31 @@ document.addEventListener("DOMContentLoaded", ()=>{
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matched.Price is not null)
|
if (matched.Price is not null)
|
||||||
{
|
{
|
||||||
item.Price = matched.Price.Value;
|
item.Price = matched.Price.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(matched.Name))
|
if (!string.IsNullOrEmpty(matched.Name))
|
||||||
{
|
{
|
||||||
item.Name = matched.Name;
|
item.Name = matched.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(matched.Description))
|
if (!string.IsNullOrEmpty(matched.Description))
|
||||||
{
|
{
|
||||||
item.Description = matched.Description;
|
item.Description = matched.Description;
|
||||||
}
|
}
|
||||||
|
|
||||||
availableToShow = true;
|
availableToShow = true;
|
||||||
specific = true;
|
specific = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!availableToShow)
|
if (!availableToShow)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
<input type="hidden" asp-for="Items[index].TicketTypeId" value="@item.Id"/>
|
<input type="hidden" asp-for="Items[index].TicketTypeId" value="@item.Id"/>
|
||||||
@@ -200,7 +157,6 @@ document.addEventListener("DOMContentLoaded", ()=>{
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -221,14 +177,18 @@ document.addEventListener("DOMContentLoaded", ()=>{
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
<div class="row text-center">
|
<footer class="store-footer">
|
||||||
<div class="col-12" id="fiat-page-link">
|
<p >
|
||||||
<a href="@Model.Event.Url">Back to fiat ticket page</a>
|
<a href="@Model.Event.Url">Back to fiat ticket page</a>
|
||||||
|
</p>
|
||||||
|
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
|
||||||
|
Powered by <partial name="_StoreFooterLogo"/>
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<div class="powered__by__btcpayserver col-12">
|
<partial name="LayoutFoot"/>
|
||||||
Powered by <a target="_blank" href="https://github.com/btcpayserver/btcpayserver" rel="noreferrer noopener">BTCPay Server</a>
|
|
||||||
</div>
|
</body>
|
||||||
</div>
|
</html>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
Reference in New Issue
Block a user