diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs index d43f47120..83fb1b320 100644 --- a/BTCPayServer/Controllers/AppsPublicController.cs +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -114,7 +114,7 @@ namespace BTCPayServer.Controllers [DomainMappingConstraint(AppType.PointOfSale)] public async Task ViewPointOfSale(string appId, PosViewType viewType, - [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal amount, + [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount, string email, string orderId, string notificationUrl, @@ -136,7 +136,7 @@ namespace BTCPayServer.Controllers return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId, viewType = viewType }); } string title = null; - var price = 0.0m; + decimal? price = null; Dictionary paymentMethods = null; ViewPointOfSaleViewModel.Item choice = null; if (!string.IsNullOrEmpty(choiceKey)) @@ -146,9 +146,17 @@ namespace BTCPayServer.Controllers if (choice == null) return NotFound(); title = choice.Title; - price = choice.Price.Value; - if (amount > price) - price = amount; + if (choice.Custom == "topup") + { + price = null; + } + else + { + price = choice.Price.Value; + if (amount > price) + price = amount; + } + if (choice.Inventory.HasValue) { @@ -277,10 +285,7 @@ namespace BTCPayServer.Controllers [DomainMappingConstraintAttribute(AppType.Crowdfund)] public async Task ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken) { - if (request.Amount <= 0) - { - return NotFound("Please provide an amount greater than 0"); - } + var app = await _AppService.GetApp(appId, AppType.Crowdfund, true); if (app == null) @@ -307,7 +312,7 @@ namespace BTCPayServer.Controllers var store = await _AppService.GetStore(app); var title = settings.Title; - var price = request.Amount; + decimal? price = request.Amount; Dictionary paymentMethods = null; ViewPointOfSaleViewModel.Item choice = null; if (!string.IsNullOrEmpty(request.ChoiceKey)) @@ -317,11 +322,17 @@ namespace BTCPayServer.Controllers if (choice == null) return NotFound("Incorrect option provided"); title = choice.Title; - price = choice.Price.Value; - if (request.Amount > price) - price = request.Amount; - + if (choice.Custom == "topup") + { + price = null; + } + else + { + price = choice.Price.Value; + if (request.Amount > price) + price = request.Amount; + } if (choice.Inventory.HasValue) { if (choice.Inventory <= 0) @@ -329,14 +340,21 @@ namespace BTCPayServer.Controllers return NotFound("Option was out of stock"); } } - - if (choice?.PaymentMethods?.Any() is true) { paymentMethods = choice?.PaymentMethods.ToDictionary(s => s, s => new InvoiceSupportedTransactionCurrency() { Enabled = true }); } } + else + { + if (request.Amount < 0) + { + return NotFound("Please provide an amount greater than 0"); + } + + price = null; + } if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price > (info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount)))) diff --git a/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs b/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs index ee28b1cec..10b37621c 100644 --- a/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs @@ -83,7 +83,7 @@ namespace BTCPayServer.Models.AppViewModels public class ContributeToCrowdfund { public ViewCrowdfundViewModel ViewCrowdfundViewModel { get; set; } - [Required] public decimal Amount { get; set; } + [Required] public decimal? Amount { get; set; } public string Email { get; set; } public string ChoiceKey { get; set; } public bool RedirectToCheckout { get; set; } diff --git a/BTCPayServer/Models/AppViewModels/ViewPointOfSaleViewModel.cs b/BTCPayServer/Models/AppViewModels/ViewPointOfSaleViewModel.cs index d06196731..47718ff55 100644 --- a/BTCPayServer/Models/AppViewModels/ViewPointOfSaleViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/ViewPointOfSaleViewModel.cs @@ -17,7 +17,7 @@ namespace BTCPayServer.Models.AppViewModels public string Image { get; set; } public ItemPrice Price { get; set; } public string Title { get; set; } - public bool Custom { get; set; } + public string Custom { get; set; } public string BuyButtonText { get; set; } public int? Inventory { get; set; } = null; public string[] PaymentMethods { get; set; } diff --git a/BTCPayServer/Services/Apps/AppService.cs b/BTCPayServer/Services/Apps/AppService.cs index 662931a00..aad704f5b 100644 --- a/BTCPayServer/Services/Apps/AppService.cs +++ b/BTCPayServer/Services/Apps/AppService.cs @@ -289,7 +289,8 @@ namespace BTCPayServer.Services.Apps { var itemNode = new YamlMappingNode(); itemNode.Add("title", new YamlScalarNode(item.Title)); - itemNode.Add("price", new YamlScalarNode(item.Price.Value.ToStringInvariant())); + if(item.Custom!= "topup") + itemNode.Add("price", new YamlScalarNode(item.Price.Value.ToStringInvariant())); if (!string.IsNullOrEmpty(item.Description)) { itemNode.Add("description", new YamlScalarNode(item.Description) @@ -341,13 +342,16 @@ namespace BTCPayServer.Services.Apps Id = c.Key, Image = c.GetDetailString("image"), Title = c.GetDetailString("title") ?? c.Key, - Price = c.GetDetail("price") - .Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice() - { - Value = decimal.Parse(cc.Value.Value, CultureInfo.InvariantCulture), - Formatted = Currencies.FormatCurrency(cc.Value.Value, currency) - }).Single(), - Custom = c.GetDetailString("custom") == "true", + Custom = c.GetDetailString("custom"), + Price = + c.GetDetailString("custom") == "topup" + ? null + : c.GetDetail("price") + .Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice() + { + Value = decimal.Parse(cc.Value.Value, CultureInfo.InvariantCulture), + Formatted = Currencies.FormatCurrency(cc.Value.Value, currency) + }).Single(), BuyButtonText = c.GetDetailString("buyButtonText"), Inventory = string.IsNullOrEmpty(c.GetDetailString("inventory")) ? (int?)null : int.Parse(c.GetDetailString("inventory"), CultureInfo.InvariantCulture), PaymentMethods = c.GetDetailStringList("payment_methods"), diff --git a/BTCPayServer/Views/Apps/TemplateEditor.cshtml b/BTCPayServer/Views/Apps/TemplateEditor.cshtml index 9604c1f5c..24a0a26d9 100644 --- a/BTCPayServer/Views/Apps/TemplateEditor.cshtml +++ b/BTCPayServer/Views/Apps/TemplateEditor.cshtml @@ -7,7 +7,7 @@ { foreach (var error in errors.Errors) { -
+
@error.ErrorMessage } } @@ -25,7 +25,7 @@
- No items.
+ No items.
@@ -46,7 +46,7 @@ @@ -120,8 +121,9 @@ document.addEventListener("DOMContentLoaded", function () { items: [], editingItem: null, customPriceOptions: [ - { text: 'No', value: false }, - { text: 'Yes', value: true }, + { text: 'Fixed', value: false }, + { text: 'Minimum', value: true }, + { text: 'Topup', value: 'topup' }, ], elementId: "@Model.templateId" }, @@ -222,7 +224,7 @@ document.addEventListener("DOMContentLoaded", function () { } } - if (price != null || title != null) { + if (title != null) { // Add product to the list result.push({ id: id, @@ -230,7 +232,7 @@ document.addEventListener("DOMContentLoaded", function () { price: price, image: image || null, description: description || '', - custom: custom === "true", + custom: custom === "topup"? "topup": custom === "true", buyButtonText: buyButtonText, inventory: isNaN(inventory)? null: inventory, paymentMethods: paymentMethods, @@ -241,13 +243,13 @@ document.addEventListener("DOMContentLoaded", function () { this.items = result; }, toYml: function(){ - var template = ''; + let template = ''; // Construct template from the product list - for (var key in this.items) { - var product = this.items[key], + for (const key in this.items) { + const product = this.items[key], id = product.id, title = product.title, - price = product.price? product.price : 0, + price = product.custom === 'topup'? null : product.price??0, image = product.image, description = product.description, custom = product.custom, @@ -255,36 +257,36 @@ document.addEventListener("DOMContentLoaded", function () { inventory = product.inventory, paymentMethods = product.paymentMethods, disabled = product.disabled; - - template += id + ':\n' + - ' price: ' + parseFloat(price).noExponents() + '\n' + - ' title: ' + title + '\n'; + let itemTemplate = id+":\n"; + itemTemplate += ( product.custom === 'topup'? '' : (' price: ' + parseFloat(price).noExponents() + '\n')); + itemTemplate+= ' title: ' + title + '\n'; if (description) { - template += ' description: "' + description.replaceAll("\n", "
").replaceAll('"', '\\"') + '"\n'; + itemTemplate += ' description: "' + description.replaceAll("\n", "
").replaceAll('"', '\\"') + '"\n'; } if (image) { - template += ' image: ' + image + '\n'; + itemTemplate += ' image: ' + image + '\n'; } if (inventory) { - template += ' inventory: ' + inventory + '\n'; + itemTemplate += ' inventory: ' + inventory + '\n'; } if (custom != null) { - template += ' custom: ' + custom + '\n'; + itemTemplate += ' custom: ' + (custom === "topup"? '"topup"': custom) + '\n'; } if (buyButtonText != null && buyButtonText.length > 0) { - template += ' buyButtonText: ' + buyButtonText + '\n'; + itemTemplate += ' buyButtonText: ' + buyButtonText + '\n'; } if (disabled != null) { - template += ' disabled: ' + disabled.toString() + '\n'; + itemTemplate += ' disabled: ' + disabled.toString() + '\n'; } if(paymentMethods != null && paymentMethods.length > 0){ - template+= ' payment_methods:\n'; + itemTemplate+= ' payment_methods:\n'; for (var method of paymentMethods){ - template+= ' - '+method+'\n'; + itemTemplate+= ' - '+method+'\n'; } } - template += '\n'; + itemTemplate += '\n'; + template+=itemTemplate; } this.getInputElement().val(template); }, @@ -337,7 +339,7 @@ document.addEventListener("DOMContentLoaded", function () { this.errors.push("Image cannot start with \"- \""); } - if (!this.$refs.txtPrice.checkValidity()) { + if (this.editingItem.custom !== "topup" && !this.$refs.txtPrice.checkValidity()) { this.errors.push("Price must be a valid number"); } if (!this.$refs.txtTitle.checkValidity()) { diff --git a/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml b/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml index 3cea79623..bc7f301f5 100644 --- a/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml +++ b/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml @@ -19,26 +19,29 @@
- @if (item.Price.Value > 0) + @if (item.Price?.Value > 0) { @item.Price.Value @vm.TargetCurrency - if (item.Custom) + if (item.Custom == "true") { @Safe.Raw("or more") } } - else if (item.Custom) + else if (item.Custom == "topup" || item.Custom == "true" ) { @Safe.Raw("Any amount") + }else if (item.Custom == "false") + { + @Safe.Raw("Free") }
diff --git a/BTCPayServer/Views/AppsPublic/PointOfSale/Cart.cshtml b/BTCPayServer/Views/AppsPublic/PointOfSale/Cart.cshtml index 14a904ec6..ed31198bf 100644 --- a/BTCPayServer/Views/AppsPublic/PointOfSale/Cart.cshtml +++ b/BTCPayServer/Views/AppsPublic/PointOfSale/Cart.cshtml @@ -230,7 +230,17 @@