From b5efb8d2e61a9184c837cd61370b4e27c7e05d76 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Fri, 31 Aug 2018 10:45:21 +0900 Subject: [PATCH 01/12] Add exchange name to expired rate --- BTCPayServer/Services/Rates/BackgroundFetcherRateProvider.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/Services/Rates/BackgroundFetcherRateProvider.cs b/BTCPayServer/Services/Rates/BackgroundFetcherRateProvider.cs index 6b658c1e2..e10c26687 100644 --- a/BTCPayServer/Services/Rates/BackgroundFetcherRateProvider.cs +++ b/BTCPayServer/Services/Rates/BackgroundFetcherRateProvider.cs @@ -18,7 +18,7 @@ namespace BTCPayServer.Services.Rates public DateTimeOffset NextRefresh; public DateTimeOffset Expiration; public Exception Exception; - + public string ExchangeName; internal ExchangeRates GetResult() { if (Expiration <= DateTimeOffset.UtcNow) @@ -29,7 +29,7 @@ namespace BTCPayServer.Services.Rates } else { - throw new InvalidOperationException("The rate has expired"); + throw new InvalidOperationException($"The rate has expired ({ExchangeName})"); } } return Latest; @@ -128,6 +128,7 @@ namespace BTCPayServer.Services.Rates { var previous = _Latest; var fetch = new LatestFetch(); + fetch.ExchangeName = GetExchangeName(); try { var rates = await _Inner.GetRatesAsync(); From 735012e3d709021acf01b89ff5373fa965f0616e Mon Sep 17 00:00:00 2001 From: rockstardev Date: Thu, 30 Aug 2018 11:34:39 -0500 Subject: [PATCH 02/12] Refactoring Invoice to cleanup unused code --- .../Controllers/InvoiceController.API.cs | 22 +++----- .../InvoiceController.PaymentProtocol.cs | 48 +++++++++++++---- .../Controllers/InvoiceController.UI.cs | 29 +++++------ BTCPayServer/Controllers/InvoiceController.cs | 52 ++++++------------- .../Controllers/PaymentRequestActionResult.cs | 40 -------------- 5 files changed, 75 insertions(+), 116 deletions(-) delete mode 100644 BTCPayServer/Controllers/PaymentRequestActionResult.cs diff --git a/BTCPayServer/Controllers/InvoiceController.API.cs b/BTCPayServer/Controllers/InvoiceController.API.cs index 20c7e797a..374f887a4 100644 --- a/BTCPayServer/Controllers/InvoiceController.API.cs +++ b/BTCPayServer/Controllers/InvoiceController.API.cs @@ -1,20 +1,14 @@ -using BTCPayServer.Authentication; -using Microsoft.Extensions.Logging; -using BTCPayServer.Filters; -using BTCPayServer.Logging; -using BTCPayServer.Models; -using Microsoft.AspNetCore.Mvc; -using NBitpayClient; -using System; -using System.Collections.Generic; +using System; using System.Linq; using System.Threading.Tasks; -using BTCPayServer.Data; -using BTCPayServer.Services.Invoices; -using Microsoft.AspNetCore.Cors; -using BTCPayServer.Services.Stores; -using Microsoft.AspNetCore.Authorization; +using BTCPayServer.Filters; +using BTCPayServer.Models; using BTCPayServer.Security; +using BTCPayServer.Services.Invoices; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using NBitpayClient; namespace BTCPayServer.Controllers { diff --git a/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs b/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs index 198ef9cb3..3ba0a4120 100644 --- a/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs +++ b/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs @@ -1,16 +1,14 @@ -using BTCPayServer.Filters; -using Microsoft.Extensions.Logging; -using BTCPayServer.Logging; -using Microsoft.AspNetCore.Mvc; -using NBitcoin; -using NBitcoin.Payment; -using System; -using System.Collections.Generic; +using System; using System.Linq; using System.Text; using System.Threading.Tasks; -using BTCPayServer.Services.Invoices; +using BTCPayServer.Filters; +using BTCPayServer.Logging; using BTCPayServer.Payments; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NBitcoin; +using NBitcoin.Payment; namespace BTCPayServer.Controllers { @@ -84,4 +82,36 @@ namespace BTCPayServer.Controllers return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase...")); } } + + + public class PaymentRequestActionResult : IActionResult + { + PaymentRequest req; + public PaymentRequestActionResult(PaymentRequest req) + { + this.req = req; + } + public Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary"; + context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest"; + req.WriteTo(context.HttpContext.Response.Body); + return Task.CompletedTask; + } + } + public class PaymentAckActionResult : IActionResult + { + PaymentACK req; + public PaymentAckActionResult(PaymentACK req) + { + this.req = req; + } + public Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary"; + context.HttpContext.Response.ContentType = "application/bitcoin-paymentack"; + req.WriteTo(context.HttpContext.Response.Body); + return Task.CompletedTask; + } + } } diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 2c887126e..084c0dfcd 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -1,28 +1,25 @@ -using BTCPayServer.Data; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Data; +using BTCPayServer.Events; using BTCPayServer.Filters; using BTCPayServer.Models.InvoicingModels; +using BTCPayServer.Payments; +using BTCPayServer.Payments.Lightning; +using BTCPayServer.Security; using BTCPayServer.Services.Invoices; +using BTCPayServer.Services.Rates; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using NBitcoin; using NBitpayClient; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; -using BTCPayServer.Services.Rates; -using System.Net.WebSockets; -using System.Threading; -using BTCPayServer.Events; using NBXplorer; -using BTCPayServer.Payments; -using BTCPayServer.Payments.Lightning; -using BTCPayServer.Security; namespace BTCPayServer.Controllers { diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index ffbd364ae..14e2d99e2 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -1,47 +1,25 @@ -using BTCPayServer.Authentication; -using System.Reflection; -using System.Linq; -using Microsoft.Extensions.Logging; -using BTCPayServer.Logging; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using NBitpayClient; -using System; +using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Text; using System.Threading.Tasks; -using BTCPayServer.Models; -using Newtonsoft.Json; -using System.Globalization; -using NBitcoin; -using NBitcoin.DataEncoders; -using BTCPayServer.Filters; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using System.Net; -using Microsoft.AspNetCore.Identity; -using Newtonsoft.Json.Linq; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using NBitcoin.Payment; using BTCPayServer.Data; -using BTCPayServer.Models.InvoicingModels; -using System.Security.Claims; -using BTCPayServer.Services; -using System.ComponentModel.DataAnnotations; -using System.Text.RegularExpressions; -using BTCPayServer.Services.Stores; -using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Rates; -using BTCPayServer.Services.Wallets; -using BTCPayServer.Validations; - -using Microsoft.EntityFrameworkCore; -using Microsoft.AspNetCore.Mvc.Routing; -using NBXplorer.DerivationStrategy; -using NBXplorer; -using BTCPayServer.HostedServices; +using BTCPayServer.Logging; +using BTCPayServer.Models; using BTCPayServer.Payments; using BTCPayServer.Rating; using BTCPayServer.Security; +using BTCPayServer.Services.Invoices; +using BTCPayServer.Services.Rates; +using BTCPayServer.Services.Stores; +using BTCPayServer.Services.Wallets; +using BTCPayServer.Validations; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using NBitcoin; +using NBitpayClient; +using Newtonsoft.Json; namespace BTCPayServer.Controllers { diff --git a/BTCPayServer/Controllers/PaymentRequestActionResult.cs b/BTCPayServer/Controllers/PaymentRequestActionResult.cs deleted file mode 100644 index 7964afa14..000000000 --- a/BTCPayServer/Controllers/PaymentRequestActionResult.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using NBitcoin.Payment; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace BTCPayServer.Controllers -{ - public class PaymentRequestActionResult : IActionResult - { - PaymentRequest req; - public PaymentRequestActionResult(PaymentRequest req) - { - this.req = req; - } - public Task ExecuteResultAsync(ActionContext context) - { - context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary"; - context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest"; - req.WriteTo(context.HttpContext.Response.Body); - return Task.CompletedTask; - } - } - public class PaymentAckActionResult : IActionResult - { - PaymentACK req; - public PaymentAckActionResult(PaymentACK req) - { - this.req = req; - } - public Task ExecuteResultAsync(ActionContext context) - { - context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary"; - context.HttpContext.Response.ContentType = "application/bitcoin-paymentack"; - req.WriteTo(context.HttpContext.Response.Body); - return Task.CompletedTask; - } - } -} From 0abd62dfe8d71e7fb273bdd26567bbf28c735fa1 Mon Sep 17 00:00:00 2001 From: rockstardev Date: Thu, 30 Aug 2018 11:53:59 -0500 Subject: [PATCH 03/12] Moving PayButton handler to public controller --- BTCPayServer/BTCPayServer.csproj | 4 +- BTCPayServer/Controllers/PublicController.cs | 62 +++++++++++++++++++ BTCPayServer/Controllers/StoresController.cs | 41 +----------- .../{Stores => Public}/PayButtonHandle.cshtml | 0 .../{Stores => Public}/PayButtonTest.cshtml | 2 +- BTCPayServer/wwwroot/paybutton/paybutton.js | 2 +- 6 files changed, 67 insertions(+), 44 deletions(-) create mode 100644 BTCPayServer/Controllers/PublicController.cs rename BTCPayServer/Views/{Stores => Public}/PayButtonHandle.cshtml (100%) rename BTCPayServer/Views/{Stores => Public}/PayButtonTest.cshtml (79%) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 5bebb05a7..d0839c056 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -127,10 +127,10 @@ $(IncludeRazorContentInPack) - + $(IncludeRazorContentInPack) - + $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Controllers/PublicController.cs b/BTCPayServer/Controllers/PublicController.cs new file mode 100644 index 000000000..1ddefaaed --- /dev/null +++ b/BTCPayServer/Controllers/PublicController.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Models.StoreViewModels; +using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; + +namespace BTCPayServer.Controllers +{ + public class PublicController : Controller + { + public PublicController(InvoiceController invoiceController, + StoreRepository storeRepository) + { + _InvoiceController = invoiceController; + _StoreRepository = storeRepository; + } + + private InvoiceController _InvoiceController; + private StoreRepository _StoreRepository; + + [HttpPost] + [Route("/pay/{storeId}")] + [IgnoreAntiforgeryToken] + [EnableCors(CorsPolicies.All)] + public async Task PayButtonHandle(string storeId, [FromForm]PayButtonViewModel model) + { + var store = await _StoreRepository.FindStore(storeId); + if (store == null) + ModelState.AddModelError("Store", "Invalid store"); + + // TODO: extract validation to model + if (model.Price <= 0) + ModelState.AddModelError("Price", "Price must be greater than 0"); + + if (!ModelState.IsValid) + return View(); + + var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice() + { + Price = model.Price, + Currency = model.Currency, + ItemDesc = model.CheckoutDesc, + OrderId = model.OrderId, + BuyerEmail = model.NotifyEmail, + NotificationURL = model.ServerIpn, + RedirectURL = model.BrowserRedirect, + FullNotifications = true + }, store, HttpContext.Request.GetAbsoluteRoot()); + return Redirect(invoice.Data.Url); + } + + [HttpGet] + [Route("/paybuttontest")] + public IActionResult PayButtonTest() + { + return View(); + } + } +} diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index fc6f2c247..897e83140 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -48,8 +48,7 @@ namespace BTCPayServer.Controllers ExplorerClientProvider explorerProvider, IFeeProviderFactory feeRateProvider, LanguageService langService, - IHostingEnvironment env, - InvoiceController invoiceController) + IHostingEnvironment env) { _RateFactory = rateFactory; _Repo = repo; @@ -65,7 +64,6 @@ namespace BTCPayServer.Controllers _ServiceProvider = serviceProvider; _BtcpayServerOptions = btcpayServerOptions; _BTCPayEnv = btcpayEnv; - _InvoiceController = invoiceController; } BTCPayServerOptions _BtcpayServerOptions; BTCPayServerEnvironment _BTCPayEnv; @@ -80,7 +78,6 @@ namespace BTCPayServer.Controllers UserManager _UserManager; private LanguageService _LangService; IHostingEnvironment _Env; - InvoiceController _InvoiceController; [TempData] public string StatusMessage @@ -791,41 +788,5 @@ namespace BTCPayServer.Controllers }; return View(model); } - - [HttpPost] - [Route("{storeId}/pay")] - [IgnoreAntiforgeryToken] - [EnableCors(CorsPolicies.All)] - public async Task PayButtonHandle(string storeId, [FromForm]PayButtonViewModel model) - { - var store = StoreData; - - // TODO: extract validation to model - if (model.Price <= 0) - ModelState.AddModelError("Price", "Price must be greater than 0"); - - if (!ModelState.IsValid) - return View(); - - var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice() - { - Price = model.Price, - Currency = model.Currency, - ItemDesc = model.CheckoutDesc, - OrderId = model.OrderId, - BuyerEmail = model.NotifyEmail, - NotificationURL = model.ServerIpn, - RedirectURL = model.BrowserRedirect, - FullNotifications = true - }, store, HttpContext.Request.GetAbsoluteRoot()); - return Redirect(invoice.Data.Url); - } - - [HttpGet] - [Route("{storeId}/paybuttontest")] - public IActionResult PayButtonTest(string storeId) - { - return View(); - } } } diff --git a/BTCPayServer/Views/Stores/PayButtonHandle.cshtml b/BTCPayServer/Views/Public/PayButtonHandle.cshtml similarity index 100% rename from BTCPayServer/Views/Stores/PayButtonHandle.cshtml rename to BTCPayServer/Views/Public/PayButtonHandle.cshtml diff --git a/BTCPayServer/Views/Stores/PayButtonTest.cshtml b/BTCPayServer/Views/Public/PayButtonTest.cshtml similarity index 79% rename from BTCPayServer/Views/Stores/PayButtonTest.cshtml rename to BTCPayServer/Views/Public/PayButtonTest.cshtml index c7eb1b307..538e5602b 100644 --- a/BTCPayServer/Views/Stores/PayButtonTest.cshtml +++ b/BTCPayServer/Views/Public/PayButtonTest.cshtml @@ -3,7 +3,7 @@
-
+ diff --git a/BTCPayServer/wwwroot/paybutton/paybutton.js b/BTCPayServer/wwwroot/paybutton/paybutton.js index ea8636959..ba544e882 100644 --- a/BTCPayServer/wwwroot/paybutton/paybutton.js +++ b/BTCPayServer/wwwroot/paybutton/paybutton.js @@ -42,7 +42,7 @@ function inputChanges(event, buttonSize) { srvModel.buttonSize = buttonSize; } - var html = ''; + var html = ''; html += addinput("price", srvModel.price); if (srvModel.currency) { html += addinput("currency", srvModel.currency); From 7341be76bb9a4a61f74bda5f0b8cea6530f1a6dc Mon Sep 17 00:00:00 2001 From: rockstardev Date: Thu, 30 Aug 2018 13:16:24 -0500 Subject: [PATCH 04/12] Extracting public portion of app controller --- BTCPayServer/BTCPayServer.csproj | 4 + .../Controllers/AppsController.PointOfSale.cs | 153 +-------------- BTCPayServer/Controllers/AppsController.cs | 45 ++--- .../Controllers/AppsPublicController.cs | 182 ++++++++++++++++++ BTCPayServer/Hosting/BTCPayServerServices.cs | 2 + BTCPayServer/Views/Apps/ListApps.cshtml | 2 +- .../ViewPointOfSale.cshtml | 2 +- 7 files changed, 215 insertions(+), 175 deletions(-) create mode 100644 BTCPayServer/Controllers/AppsPublicController.cs rename BTCPayServer/Views/{Apps => AppsPublic}/ViewPointOfSale.cshtml (97%) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index d0839c056..f84b96672 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -121,6 +121,10 @@ + + PreserveNewest + $(IncludeRazorContentInPack) + $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Controllers/AppsController.PointOfSale.cs b/BTCPayServer/Controllers/AppsController.PointOfSale.cs index bd6dadad7..c65f50329 100644 --- a/BTCPayServer/Controllers/AppsController.PointOfSale.cs +++ b/BTCPayServer/Controllers/AppsController.PointOfSale.cs @@ -1,25 +1,11 @@ -using System; -using Microsoft.EntityFrameworkCore; -using System.Collections.Generic; -using System.Linq; +using System.Text; +using System.Text.Encodings.Web; using System.Threading.Tasks; using BTCPayServer.Data; -using BTCPayServer.Models; using BTCPayServer.Models.AppViewModels; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using NBitcoin.DataEncoders; -using NBitcoin; using BTCPayServer.Services.Apps; -using Newtonsoft.Json; -using YamlDotNet.RepresentationModel; -using System.IO; -using BTCPayServer.Services.Rates; -using System.Globalization; -using System.Text; -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Controllers { @@ -87,7 +73,7 @@ namespace BTCPayServer.Controllers } try { - var items = Parse(settings.Template, settings.Currency); + var items = _AppsHelper.Parse(settings.Template, settings.Currency); var builder = new StringBuilder(); builder.AppendLine($""); builder.AppendLine($" "); @@ -109,11 +95,11 @@ namespace BTCPayServer.Controllers [Route("{appId}/settings/pos")] public async Task UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm) { - if (_Currencies.GetCurrencyData(vm.Currency, false) == null) + if (_AppsHelper.GetCurrencyData(vm.Currency, false) == null) ModelState.AddModelError(nameof(vm.Currency), "Invalid currency"); try { - Parse(vm.Template, vm.Currency); + _AppsHelper.Parse(vm.Template, vm.Currency); } catch { @@ -138,131 +124,6 @@ namespace BTCPayServer.Controllers return RedirectToAction(nameof(ListApps)); } - [HttpGet] - [Route("{appId}/pos")] - public async Task ViewPointOfSale(string appId) - { - var app = await GetApp(appId, AppType.PointOfSale); - if (app == null) - return NotFound(); - var settings = app.GetSettings(); - var currency = _Currencies.GetCurrencyData(settings.Currency, false); - double step = currency == null ? 1 : Math.Pow(10, -(currency.Divisibility)); - - return View(new ViewPointOfSaleViewModel() - { - Title = settings.Title, - Step = step.ToString(CultureInfo.InvariantCulture), - ShowCustomAmount = settings.ShowCustomAmount, - Items = Parse(settings.Template, settings.Currency) - }); - } - - private async Task GetApp(string appId, AppType appType) - { - using (var ctx = _ContextFactory.CreateContext()) - { - return await ctx.Apps - .Where(us => us.Id == appId && - us.AppType == appType.ToString()) - .FirstOrDefaultAsync(); - } - } - - private ViewPointOfSaleViewModel.Item[] Parse(string template, string currency) - { - var input = new StringReader(template); - YamlStream stream = new YamlStream(); - stream.Load(input); - var root = (YamlMappingNode)stream.Documents[0].RootNode; - return root - .Children - .Select(kv => new { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlMappingNode }) - .Where(kv => kv.Value != null) - .Select(c => new ViewPointOfSaleViewModel.Item() - { - Id = c.Key, - Title = c.Value.Children - .Select(kv => new { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlScalarNode }) - .Where(kv => kv.Value != null) - .Where(cc => cc.Key == "title") - .FirstOrDefault()?.Value?.Value ?? c.Key, - Price = c.Value.Children - .Select(kv => new { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlScalarNode }) - .Where(kv => kv.Value != null) - .Where(cc => cc.Key == "price") - .Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice() - { - Value = decimal.Parse(cc.Value.Value, CultureInfo.InvariantCulture), - Formatted = FormatCurrency(cc.Value.Value, currency) - }) - .Single() - }) - .ToArray(); - } - - string FormatCurrency(string price, string currency) - { - return decimal.Parse(price, CultureInfo.InvariantCulture).ToString("C", _Currencies.GetCurrencyProvider(currency)); - } - - [HttpPost] - [Route("{appId}/pos")] - [IgnoreAntiforgeryToken] - [EnableCors(CorsPolicies.All)] - public async Task ViewPointOfSale(string appId, - decimal amount, - string email, - string orderId, - string notificationUrl, - string redirectUrl, - string choiceKey) - { - var app = await GetApp(appId, AppType.PointOfSale); - if (string.IsNullOrEmpty(choiceKey) && amount <= 0) - { - return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); - } - if (app == null) - return NotFound(); - var settings = app.GetSettings(); - if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount) - { - return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); - } - string title = null; - var price = 0.0m; - if (!string.IsNullOrEmpty(choiceKey)) - { - var choices = Parse(settings.Template, settings.Currency); - var choice = choices.FirstOrDefault(c => c.Id == choiceKey); - if (choice == null) - return NotFound(); - title = choice.Title; - price = choice.Price.Value; - } - else - { - if (!settings.ShowCustomAmount) - return NotFound(); - price = amount; - title = settings.Title; - } - var store = await GetStore(app); - var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice() - { - ItemDesc = title, - Currency = settings.Currency, - Price = price, - BuyerEmail = email, - OrderId = orderId, - NotificationURL = notificationUrl, - RedirectURL = redirectUrl, - FullNotifications = true - }, store, HttpContext.Request.GetAbsoluteRoot()); - return Redirect(invoice.Data.Url); - } - private async Task UpdateAppSettings(AppData app) { using (var ctx = _ContextFactory.CreateContext()) diff --git a/BTCPayServer/Controllers/AppsController.cs b/BTCPayServer/Controllers/AppsController.cs index 4677e2480..6d3af112a 100644 --- a/BTCPayServer/Controllers/AppsController.cs +++ b/BTCPayServer/Controllers/AppsController.cs @@ -5,8 +5,9 @@ using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Models; using BTCPayServer.Models.AppViewModels; +using BTCPayServer.Security; using BTCPayServer.Services.Apps; -using BTCPayServer.Services.Rates; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -19,28 +20,26 @@ namespace BTCPayServer.Controllers [Route("apps")] public partial class AppsController : Controller { - ApplicationDbContextFactory _ContextFactory; - UserManager _UserManager; - CurrencyNameTable _Currencies; - InvoiceController _InvoiceController; - BTCPayNetworkProvider _NetworkProvider; + public AppsController( + UserManager userManager, + ApplicationDbContextFactory contextFactory, + BTCPayNetworkProvider networkProvider, + AppsHelper appsHelper) + { + _UserManager = userManager; + _ContextFactory = contextFactory; + _NetworkProvider = networkProvider; + _AppsHelper = appsHelper; + } + + private UserManager _UserManager; + private ApplicationDbContextFactory _ContextFactory; + private BTCPayNetworkProvider _NetworkProvider; + private AppsHelper _AppsHelper; [TempData] public string StatusMessage { get; set; } - public AppsController( - UserManager userManager, - ApplicationDbContextFactory contextFactory, - CurrencyNameTable currencies, - InvoiceController invoiceController, - BTCPayNetworkProvider networkProvider) - { - _InvoiceController = invoiceController; - _UserManager = userManager; - _ContextFactory = contextFactory; - _Currencies = currencies; - _NetworkProvider = networkProvider; - } public async Task ListApps() { var apps = await GetAllApps(); @@ -201,13 +200,5 @@ namespace BTCPayServer.Controllers { return _UserManager.GetUserId(User); } - - private async Task GetStore(AppData app) - { - using (var ctx = _ContextFactory.CreateContext()) - { - return await ctx.Stores.FirstOrDefaultAsync(s => s.Id == app.StoreDataId); - } - } } } diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs new file mode 100644 index 000000000..dceca27d4 --- /dev/null +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -0,0 +1,182 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Data; +using BTCPayServer.Models.AppViewModels; +using BTCPayServer.Services.Apps; +using BTCPayServer.Services.Rates; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using YamlDotNet.RepresentationModel; +using static BTCPayServer.Controllers.AppsController; + +namespace BTCPayServer.Controllers +{ + public class AppsPublicController : Controller + { + public AppsPublicController(AppsHelper appsHelper, InvoiceController invoiceController) + { + _AppsHelper = appsHelper; + _InvoiceController = invoiceController; + } + + private AppsHelper _AppsHelper; + private InvoiceController _InvoiceController; + + [HttpGet] + [Route("/apps/{appId}/pos")] + public async Task ViewPointOfSale(string appId) + { + var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale); + if (app == null) + return NotFound(); + var settings = app.GetSettings(); + var currency = _AppsHelper.GetCurrencyData(settings.Currency, false); + double step = currency == null ? 1 : Math.Pow(10, -(currency.Divisibility)); + + return View(new ViewPointOfSaleViewModel() + { + Title = settings.Title, + Step = step.ToString(CultureInfo.InvariantCulture), + ShowCustomAmount = settings.ShowCustomAmount, + Items = _AppsHelper.Parse(settings.Template, settings.Currency) + }); + } + + [HttpPost] + [Route("/apps/{appId}/pos")] + [IgnoreAntiforgeryToken] + [EnableCors(CorsPolicies.All)] + public async Task ViewPointOfSale(string appId, + decimal amount, + string email, + string orderId, + string notificationUrl, + string redirectUrl, + string choiceKey) + { + var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale); + if (string.IsNullOrEmpty(choiceKey) && amount <= 0) + { + return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); + } + if (app == null) + return NotFound(); + var settings = app.GetSettings(); + if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount) + { + return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); + } + string title = null; + var price = 0.0m; + if (!string.IsNullOrEmpty(choiceKey)) + { + var choices = _AppsHelper.Parse(settings.Template, settings.Currency); + var choice = choices.FirstOrDefault(c => c.Id == choiceKey); + if (choice == null) + return NotFound(); + title = choice.Title; + price = choice.Price.Value; + } + else + { + if (!settings.ShowCustomAmount) + return NotFound(); + price = amount; + title = settings.Title; + } + var store = await _AppsHelper.GetStore(app); + var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice() + { + ItemDesc = title, + Currency = settings.Currency, + Price = price, + BuyerEmail = email, + OrderId = orderId, + NotificationURL = notificationUrl, + RedirectURL = redirectUrl, + FullNotifications = true + }, store, HttpContext.Request.GetAbsoluteRoot()); + return Redirect(invoice.Data.Url); + } + } + + + public class AppsHelper + { + ApplicationDbContextFactory _ContextFactory; + CurrencyNameTable _Currencies; + + public AppsHelper(ApplicationDbContextFactory contextFactory, CurrencyNameTable currencies) + { + _ContextFactory = contextFactory; + _Currencies = currencies; + + } + + public async Task GetApp(string appId, AppType appType) + { + using (var ctx = _ContextFactory.CreateContext()) + { + return await ctx.Apps + .Where(us => us.Id == appId && + us.AppType == appType.ToString()) + .FirstOrDefaultAsync(); + } + } + + public async Task GetStore(AppData app) + { + using (var ctx = _ContextFactory.CreateContext()) + { + return await ctx.Stores.FirstOrDefaultAsync(s => s.Id == app.StoreDataId); + } + } + + public ViewPointOfSaleViewModel.Item[] Parse(string template, string currency) + { + var input = new StringReader(template); + YamlStream stream = new YamlStream(); + stream.Load(input); + var root = (YamlMappingNode)stream.Documents[0].RootNode; + return root + .Children + .Select(kv => new { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlMappingNode }) + .Where(kv => kv.Value != null) + .Select(c => new ViewPointOfSaleViewModel.Item() + { + Id = c.Key, + Title = c.Value.Children + .Select(kv => new { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlScalarNode }) + .Where(kv => kv.Value != null) + .Where(cc => cc.Key == "title") + .FirstOrDefault()?.Value?.Value ?? c.Key, + Price = c.Value.Children + .Select(kv => new { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlScalarNode }) + .Where(kv => kv.Value != null) + .Where(cc => cc.Key == "price") + .Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice() + { + Value = decimal.Parse(cc.Value.Value, CultureInfo.InvariantCulture), + Formatted = FormatCurrency(cc.Value.Value, currency) + }) + .Single() + }) + .ToArray(); + } + + public string FormatCurrency(string price, string currency) + { + return decimal.Parse(price, CultureInfo.InvariantCulture).ToString("C", _Currencies.GetCurrencyProvider(currency)); + } + + public CurrencyData GetCurrencyData(string currency, bool useFallback) + { + return _Currencies.GetCurrencyData(currency, useFallback); + } + } +} diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index d511fb90c..80569bec4 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -95,6 +95,8 @@ namespace BTCPayServer.Hosting return opts.NetworkProvider; }); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/BTCPayServer/Views/Apps/ListApps.cshtml b/BTCPayServer/Views/Apps/ListApps.cshtml index 6f82944aa..b5aaa7e36 100644 --- a/BTCPayServer/Views/Apps/ListApps.cshtml +++ b/BTCPayServer/Views/Apps/ListApps.cshtml @@ -53,7 +53,7 @@ { Settings - } - View - + View - Remove diff --git a/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml b/BTCPayServer/Views/AppsPublic/ViewPointOfSale.cshtml similarity index 97% rename from BTCPayServer/Views/Apps/ViewPointOfSale.cshtml rename to BTCPayServer/Views/AppsPublic/ViewPointOfSale.cshtml index a4445ca83..29af7f137 100644 --- a/BTCPayServer/Views/Apps/ViewPointOfSale.cshtml +++ b/BTCPayServer/Views/AppsPublic/ViewPointOfSale.cshtml @@ -1,6 +1,6 @@ @inject BTCPayServer.HostedServices.CssThemeManager themeManager -@model ViewPointOfSaleViewModel +@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel @{ ViewData["Title"] = Model.Title; Layout = null; From 43d34d5d35fc40c3dd8398ac85edd4b2d6259f47 Mon Sep 17 00:00:00 2001 From: rockstardev Date: Thu, 30 Aug 2018 13:16:42 -0500 Subject: [PATCH 05/12] Now requiring Authorization on AppsController --- BTCPayServer/Controllers/AppsController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BTCPayServer/Controllers/AppsController.cs b/BTCPayServer/Controllers/AppsController.cs index 6d3af112a..3281e49cd 100644 --- a/BTCPayServer/Controllers/AppsController.cs +++ b/BTCPayServer/Controllers/AppsController.cs @@ -16,6 +16,7 @@ using NBitcoin.DataEncoders; namespace BTCPayServer.Controllers { + [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] [AutoValidateAntiforgeryToken] [Route("apps")] public partial class AppsController : Controller From e39d9067f2c327c9c889d4f72475be07ab332aa1 Mon Sep 17 00:00:00 2001 From: rockstardev Date: Thu, 30 Aug 2018 13:16:46 -0500 Subject: [PATCH 06/12] Updating Unit tests --- BTCPayServer.Tests/UnitTest1.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index aa7f44667..dcc5b9c1d 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -1318,14 +1318,16 @@ namespace BTCPayServer.Tests Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); vmpos = Assert.IsType(Assert.IsType(apps.UpdatePointOfSale(appId).Result).Model); Assert.Equal("hello", vmpos.Title); - var vmview = Assert.IsType(Assert.IsType(apps.ViewPointOfSale(appId).Result).Model); + + var publicApps = user.GetController(); + var vmview = Assert.IsType(Assert.IsType(publicApps.ViewPointOfSale(appId).Result).Model); Assert.Equal("hello", vmview.Title); Assert.Equal(2, vmview.Items.Length); Assert.Equal("good apple", vmview.Items[0].Title); Assert.Equal("orange", vmview.Items[1].Title); Assert.Equal(10.0m, vmview.Items[1].Price.Value); Assert.Equal("$5.00", vmview.Items[0].Price.Formatted); - Assert.IsType(apps.ViewPointOfSale(appId, 0, null, null, null, null, "orange").Result); + Assert.IsType(publicApps.ViewPointOfSale(appId, 0, null, null, null, null, "orange").Result); var invoice = user.BitPay.GetInvoices().First(); Assert.Equal(10.00m, invoice.Price); Assert.Equal("CAD", invoice.Currency); From b5626ef01c0f3cdc3c9173d83ac1640436efb1a7 Mon Sep 17 00:00:00 2001 From: rockstardev Date: Mon, 3 Sep 2018 23:18:07 -0500 Subject: [PATCH 07/12] Validating that Store has Pay Button enabled --- BTCPayServer/Controllers/PublicController.cs | 10 ++++++++-- BTCPayServer/Data/StoreData.cs | 2 ++ BTCPayServer/Views/Public/PayButtonTest.cshtml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/BTCPayServer/Controllers/PublicController.cs b/BTCPayServer/Controllers/PublicController.cs index 1ddefaaed..0c44224dd 100644 --- a/BTCPayServer/Controllers/PublicController.cs +++ b/BTCPayServer/Controllers/PublicController.cs @@ -17,7 +17,7 @@ namespace BTCPayServer.Controllers _InvoiceController = invoiceController; _StoreRepository = storeRepository; } - + private InvoiceController _InvoiceController; private StoreRepository _StoreRepository; @@ -30,9 +30,15 @@ namespace BTCPayServer.Controllers var store = await _StoreRepository.FindStore(storeId); if (store == null) ModelState.AddModelError("Store", "Invalid store"); + else + { + var storeBlob = store.GetStoreBlob(); + if (!storeBlob.PayButtonEnabled) + ModelState.AddModelError("Store", "Store has not enabled Pay Button"); + } // TODO: extract validation to model - if (model.Price <= 0) + if (model == null || model.Price <= 0) ModelState.AddModelError("Price", "Price must be greater than 0"); if (!ModelState.IsValid) diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index 902b5dacc..ae1d69a39 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -302,6 +302,8 @@ namespace BTCPayServer.Data public string RateScript { get; set; } + public bool PayButtonEnabled { get; set; } + string _LightningDescriptionTemplate; public string LightningDescriptionTemplate diff --git a/BTCPayServer/Views/Public/PayButtonTest.cshtml b/BTCPayServer/Views/Public/PayButtonTest.cshtml index 538e5602b..22993ce11 100644 --- a/BTCPayServer/Views/Public/PayButtonTest.cshtml +++ b/BTCPayServer/Views/Public/PayButtonTest.cshtml @@ -3,7 +3,7 @@
- + From deb56e16ece9b72a46585b0273a05c8043074a1b Mon Sep 17 00:00:00 2001 From: rockstardev Date: Mon, 3 Sep 2018 23:48:53 -0500 Subject: [PATCH 08/12] Confirmation page for enabling Pay Button --- BTCPayServer/BTCPayServer.csproj | 3 +++ BTCPayServer/Controllers/StoresController.cs | 25 +++++++++++++++++++ BTCPayServer/Views/Stores/PayButton.cshtml | 3 ++- .../Views/Stores/PayButtonEnable.cshtml | 22 ++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 BTCPayServer/Views/Stores/PayButtonEnable.cshtml diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index f84b96672..4a8282484 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -127,6 +127,9 @@ $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 897e83140..a431ae7e9 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -776,6 +776,12 @@ namespace BTCPayServer.Controllers { var store = StoreData; + var storeBlob = store.GetStoreBlob(); + if (!storeBlob.PayButtonEnabled) + { + return View("PayButtonEnable", null); + } + var appUrl = HttpContext.Request.GetAbsoluteRoot().WithTrailingSlash(); var model = new PayButtonViewModel { @@ -788,5 +794,24 @@ namespace BTCPayServer.Controllers }; return View(model); } + + [HttpPost] + [Route("{storeId}/paybutton")] + public async Task PayButton(bool enableStore) + { + var blob = StoreData.GetStoreBlob(); + blob.PayButtonEnabled = enableStore; + if (StoreData.SetStoreBlob(blob)) + { + await _Repo.UpdateStore(StoreData); + StatusMessage = "Store successfully updated"; + } + + return RedirectToAction(nameof(PayButton), new + { + storeId = StoreData.Id + }); + + } } } diff --git a/BTCPayServer/Views/Stores/PayButton.cshtml b/BTCPayServer/Views/Stores/PayButton.cshtml index 98b319125..b2049ec92 100644 --- a/BTCPayServer/Views/Stores/PayButton.cshtml +++ b/BTCPayServer/Views/Stores/PayButton.cshtml @@ -1,7 +1,8 @@ @model PayButtonViewModel @{ Layout = "../Shared/_NavLayout.cshtml"; - ViewData.SetActivePageAndTitle(StoreNavPages.PayButton, "Pay Button"); + ViewData.SetActivePageAndTitle(StoreNavPages.PayButton); + ViewBag.MainTitle = "Pay Button"; }
diff --git a/BTCPayServer/Views/Stores/PayButtonEnable.cshtml b/BTCPayServer/Views/Stores/PayButtonEnable.cshtml new file mode 100644 index 000000000..57c9f3e6c --- /dev/null +++ b/BTCPayServer/Views/Stores/PayButtonEnable.cshtml @@ -0,0 +1,22 @@ +@{ + Layout = "../Shared/_NavLayout.cshtml"; + ViewData.SetActivePageAndTitle(StoreNavPages.PayButton, "Enable access"); + ViewBag.MainTitle = "Confirm enabling of Pay Button"; +} + +

@ViewData["Title"]

+ +
+
+ +
+

+ To start using Pay Button you need to explicitly enable creation of Invoices by Pay Button. + Once you do so, valid POST requests from any source will allow creation of invoices on your instance of BtcPayServer. +

+ + +
+ +
+
From c5cb32f6dd1eda640ddf41daee15f3da48083a4b Mon Sep 17 00:00:00 2001 From: rockstardev Date: Tue, 4 Sep 2018 00:00:20 -0500 Subject: [PATCH 09/12] Providing option to disable Pay Button at later date --- BTCPayServer/Views/Stores/PayButton.cshtml | 21 +++++++++++++++++-- .../Views/Stores/PayButtonEnable.cshtml | 10 ++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/BTCPayServer/Views/Stores/PayButton.cshtml b/BTCPayServer/Views/Stores/PayButton.cshtml index b2049ec92..f6a5e8e38 100644 --- a/BTCPayServer/Views/Stores/PayButton.cshtml +++ b/BTCPayServer/Views/Stores/PayButton.cshtml @@ -121,8 +121,25 @@ Please fix errors shown in order for code generation to successfully execute.
-
-
+ +

+
+

Disable Pay Button

+
+
+
+
+

+ Disabling this feature will cause your currently used Pay Buttons to stop working. + Customers trying to use Pay Button to create Invoices will be displayed appropriate message. + You can always reenable Pay Buttons at later time. +

+ @Html.Hidden("EnableStore", false) + +
+
+
+
@section HeadScripts { diff --git a/BTCPayServer/Views/Stores/PayButtonEnable.cshtml b/BTCPayServer/Views/Stores/PayButtonEnable.cshtml index 57c9f3e6c..bfd326248 100644 --- a/BTCPayServer/Views/Stores/PayButtonEnable.cshtml +++ b/BTCPayServer/Views/Stores/PayButtonEnable.cshtml @@ -1,7 +1,7 @@ @{ Layout = "../Shared/_NavLayout.cshtml"; - ViewData.SetActivePageAndTitle(StoreNavPages.PayButton, "Enable access"); - ViewBag.MainTitle = "Confirm enabling of Pay Button"; + ViewData.SetActivePageAndTitle(StoreNavPages.PayButton, "Please confirm you want to enable Pay Button"); + ViewBag.MainTitle = "Pay Button"; }

@ViewData["Title"]

@@ -11,10 +11,10 @@

- To start using Pay Button you need to explicitly enable creation of Invoices by Pay Button. - Once you do so, valid POST requests from any source will allow creation of invoices on your instance of BtcPayServer. + To start using Pay Buttons you need to explicitly turn on this feature. + Once you do so, valid POST requests from any source will allow creation of Invoices on your instance of BtcPayServer.

- + @Html.Hidden("EnableStore", true)
From e86b4d89ca2e44463fd52cba1b3a96bebe64bfff Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 4 Sep 2018 19:08:21 +0900 Subject: [PATCH 10/12] remove paybuttontest --- BTCPayServer/BTCPayServer.csproj | 3 --- BTCPayServer/Controllers/PublicController.cs | 7 ------- BTCPayServer/Views/Public/PayButtonTest.cshtml | 14 -------------- 3 files changed, 24 deletions(-) delete mode 100644 BTCPayServer/Views/Public/PayButtonTest.cshtml diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 4a8282484..5bc847680 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -137,9 +137,6 @@ $(IncludeRazorContentInPack) - - $(IncludeRazorContentInPack) - $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Controllers/PublicController.cs b/BTCPayServer/Controllers/PublicController.cs index 0c44224dd..f9802ba9d 100644 --- a/BTCPayServer/Controllers/PublicController.cs +++ b/BTCPayServer/Controllers/PublicController.cs @@ -57,12 +57,5 @@ namespace BTCPayServer.Controllers }, store, HttpContext.Request.GetAbsoluteRoot()); return Redirect(invoice.Data.Url); } - - [HttpGet] - [Route("/paybuttontest")] - public IActionResult PayButtonTest() - { - return View(); - } } } diff --git a/BTCPayServer/Views/Public/PayButtonTest.cshtml b/BTCPayServer/Views/Public/PayButtonTest.cshtml deleted file mode 100644 index 22993ce11..000000000 --- a/BTCPayServer/Views/Public/PayButtonTest.cshtml +++ /dev/null @@ -1,14 +0,0 @@ - -
-
-
- -
- - - -
- -
-
-
From fed53661b388ac341a24883778f47435078ddd3f Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 8 Sep 2018 14:32:26 +0900 Subject: [PATCH 11/12] Add btcpay.store.cancreateinvoice claim, and use that for the store --- .../Controllers/AppsPublicController.cs | 5 ++++- .../Controllers/InvoiceController.API.cs | 2 +- .../Controllers/InvoiceController.UI.cs | 2 +- BTCPayServer/Controllers/InvoiceController.cs | 2 ++ BTCPayServer/Controllers/PublicController.cs | 2 +- BTCPayServer/Controllers/StoresController.cs | 6 ++++-- BTCPayServer/Data/StoreData.cs | 14 +++++++++----- .../Models/StoreViewModels/StoreViewModel.cs | 3 +++ BTCPayServer/Security/BitpayAuthentication.cs | 3 ++- BTCPayServer/Security/Policies.cs | 11 ++++++----- .../Services/Invoices/InvoiceRepository.cs | 8 ++++---- BTCPayServer/Views/Stores/PayButton.cshtml | 19 ------------------- .../Views/Stores/PayButtonEnable.cshtml | 6 +++--- BTCPayServer/Views/Stores/UpdateStore.cshtml | 4 ++++ 14 files changed, 44 insertions(+), 43 deletions(-) diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs index dceca27d4..18b2f98a3 100644 --- a/BTCPayServer/Controllers/AppsPublicController.cs +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -2,9 +2,11 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Models.AppViewModels; +using BTCPayServer.Security; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Rates; using Microsoft.AspNetCore.Authorization; @@ -46,7 +48,7 @@ namespace BTCPayServer.Controllers Items = _AppsHelper.Parse(settings.Template, settings.Currency) }); } - + [HttpPost] [Route("/apps/{appId}/pos")] [IgnoreAntiforgeryToken] @@ -90,6 +92,7 @@ namespace BTCPayServer.Controllers title = settings.Title; } var store = await _AppsHelper.GetStore(app); + store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id)); var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice() { ItemDesc = title, diff --git a/BTCPayServer/Controllers/InvoiceController.API.cs b/BTCPayServer/Controllers/InvoiceController.API.cs index 374f887a4..da7bc8524 100644 --- a/BTCPayServer/Controllers/InvoiceController.API.cs +++ b/BTCPayServer/Controllers/InvoiceController.API.cs @@ -14,7 +14,7 @@ namespace BTCPayServer.Controllers { [EnableCors("BitpayAPI")] [BitpayAPIConstraint] - [Authorize(Policies.CanUseStore.Key, AuthenticationSchemes = Policies.BitpayAuthentication)] + [Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = Policies.BitpayAuthentication)] public class InvoiceControllerAPI : Controller { private InvoiceController _InvoiceController; diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 084c0dfcd..c430cb8cf 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -499,7 +499,7 @@ namespace BTCPayServer.Controllers return View(model); } StatusMessage = null; - if (!store.HasClaim(Policies.CanModifyStoreSettings.Key)) + if (!store.HasClaim(Policies.CanCreateInvoice.Key)) { ModelState.AddModelError(nameof(model.StoreId), "You need to be owner of this store to create an invoice"); return View(model); diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index 14e2d99e2..29ee0f9e5 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -62,6 +62,8 @@ namespace BTCPayServer.Controllers internal async Task> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { + if (!store.HasClaim(Policies.CanCreateInvoice.Key)) + throw new UnauthorizedAccessException(); InvoiceLogs logs = new InvoiceLogs(); logs.Write("Creation of invoice starting"); var entity = new InvoiceEntity diff --git a/BTCPayServer/Controllers/PublicController.cs b/BTCPayServer/Controllers/PublicController.cs index f9802ba9d..bec4d906b 100644 --- a/BTCPayServer/Controllers/PublicController.cs +++ b/BTCPayServer/Controllers/PublicController.cs @@ -33,7 +33,7 @@ namespace BTCPayServer.Controllers else { var storeBlob = store.GetStoreBlob(); - if (!storeBlob.PayButtonEnabled) + if (!storeBlob.AnyoneCanInvoice) ModelState.AddModelError("Store", "Store has not enabled Pay Button"); } diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index a431ae7e9..a50ad4b21 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -399,6 +399,7 @@ namespace BTCPayServer.Controllers vm.StoreName = store.StoreName; vm.StoreWebsite = store.StoreWebsite; vm.NetworkFee = !storeBlob.NetworkFeeDisabled; + vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice; vm.SpeedPolicy = store.SpeedPolicy; vm.CanDelete = _Repo.CanDeleteStores(); AddPaymentMethods(store, storeBlob, vm); @@ -470,6 +471,7 @@ namespace BTCPayServer.Controllers } var blob = StoreData.GetStoreBlob(); + blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice; blob.NetworkFeeDisabled = !model.NetworkFee; blob.MonitoringExpiration = model.MonitoringExpiration; blob.InvoiceExpiration = model.InvoiceExpiration; @@ -777,7 +779,7 @@ namespace BTCPayServer.Controllers var store = StoreData; var storeBlob = store.GetStoreBlob(); - if (!storeBlob.PayButtonEnabled) + if (!storeBlob.AnyoneCanInvoice) { return View("PayButtonEnable", null); } @@ -800,7 +802,7 @@ namespace BTCPayServer.Controllers public async Task PayButton(bool enableStore) { var blob = StoreData.GetStoreBlob(); - blob.PayButtonEnabled = enableStore; + blob.AnyoneCanInvoice = enableStore; if (StoreData.SetStoreBlob(blob)) { await _Repo.UpdateStore(StoreData); diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index ae1d69a39..19fcdfc25 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -166,24 +166,25 @@ namespace BTCPayServer.Data public Claim[] GetClaims() { List claims = new List(); + claims.AddRange(AdditionalClaims); #pragma warning disable CS0612 // Type or member is obsolete var role = Role; #pragma warning restore CS0612 // Type or member is obsolete if (role == StoreRoles.Owner) { claims.Add(new Claim(Policies.CanModifyStoreSettings.Key, Id)); - claims.Add(new Claim(Policies.CanUseStore.Key, Id)); } - if (role == StoreRoles.Guest) + + if(role == StoreRoles.Owner || role == StoreRoles.Guest || GetStoreBlob().AnyoneCanInvoice) { - claims.Add(new Claim(Policies.CanUseStore.Key, Id)); + claims.Add(new Claim(Policies.CanCreateInvoice.Key, Id)); } return claims.ToArray(); } public bool HasClaim(string claim) { - return GetClaims().Any(c => c.Type == claim); + return GetClaims().Any(c => c.Type == claim && c.Value == Id); } public byte[] StoreBlob @@ -196,6 +197,9 @@ namespace BTCPayServer.Data public List PairedSINs { get; set; } public IEnumerable APIKeys { get; set; } + [NotMapped] + public List AdditionalClaims { get; set; } = new List(); + #pragma warning disable CS0618 public string GetDefaultCrypto(BTCPayNetworkProvider networkProvider = null) { @@ -302,7 +306,7 @@ namespace BTCPayServer.Data public string RateScript { get; set; } - public bool PayButtonEnabled { get; set; } + public bool AnyoneCanInvoice { get; set; } string _LightningDescriptionTemplate; diff --git a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs index 1c8e31c0f..9401bb957 100644 --- a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs @@ -47,6 +47,9 @@ namespace BTCPayServer.Models.StoreViewModels set; } + [Display(Name = "Allow anyone to create invoice")] + public bool AnyoneCanCreateInvoice { get; set; } + public List DerivationSchemes { get; set; } = new List(); [Display(Name = "Invoice expires if the full amount has not been paid after ... minutes")] diff --git a/BTCPayServer/Security/BitpayAuthentication.cs b/BTCPayServer/Security/BitpayAuthentication.cs index d85503284..2fac207ad 100644 --- a/BTCPayServer/Security/BitpayAuthentication.cs +++ b/BTCPayServer/Security/BitpayAuthentication.cs @@ -88,8 +88,9 @@ namespace BTCPayServer.Security { if (storeId != null) { - claims.Add(new Claim(Policies.CanUseStore.Key, storeId)); + claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId)); var store = await _StoreRepository.FindStore(storeId); + store.AdditionalClaims.AddRange(claims); Context.Request.HttpContext.SetStoreData(store); } return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication)); diff --git a/BTCPayServer/Security/Policies.cs b/BTCPayServer/Security/Policies.cs index d41c234e2..f9ce9a1f7 100644 --- a/BTCPayServer/Security/Policies.cs +++ b/BTCPayServer/Security/Policies.cs @@ -12,9 +12,9 @@ namespace BTCPayServer.Security public const string CookieAuthentication = "Identity.Application"; public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options) { - AddClaim(options, CanUseStore.Key); AddClaim(options, CanModifyStoreSettings.Key); AddClaim(options, CanModifyServerSettings.Key); + AddClaim(options, CanCreateInvoice.Key); return options; } @@ -27,13 +27,14 @@ namespace BTCPayServer.Security { public const string Key = "btcpay.store.canmodifyserversettings"; } - public class CanUseStore - { - public const string Key = "btcpay.store.canusestore"; - } public class CanModifyStoreSettings { public const string Key = "btcpay.store.canmodifystoresettings"; } + + public class CanCreateInvoice + { + public const string Key = "btcpay.store.cancreateinvoice"; + } } } diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 1ef1b4764..3a68bf032 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -148,7 +148,7 @@ namespace BTCPayServer.Services.Invoices } context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id }); - foreach(var log in creationLogs.ToList()) + foreach (var log in creationLogs.ToList()) { context.InvoiceEvents.Add(new InvoiceEventData() { @@ -249,7 +249,7 @@ namespace BTCPayServer.Services.Invoices { await context.SaveChangesAsync(); } - catch(DbUpdateException) { } // Probably the invoice does not exists anymore + catch (DbUpdateException) { } // Probably the invoice does not exists anymore } } @@ -441,7 +441,7 @@ namespace BTCPayServer.Services.Invoices query = query.Where(i => statusSet.Contains(i.Status)); } - if(queryObject.Unusual != null) + if (queryObject.Unusual != null) { var unused = queryObject.Unusual.Value; query = query.Where(i => unused == (i.Status == "invalid" || i.ExceptionStatus != null)); @@ -554,7 +554,7 @@ namespace BTCPayServer.Services.Invoices { await context.SaveChangesAsync().ConfigureAwait(false); } - catch(DbUpdateException) { return null; } // Already exists + catch (DbUpdateException) { return null; } // Already exists AddToTextSearch(invoiceId, paymentData.GetSearchTerms()); return entity; } diff --git a/BTCPayServer/Views/Stores/PayButton.cshtml b/BTCPayServer/Views/Stores/PayButton.cshtml index f6a5e8e38..c2768e610 100644 --- a/BTCPayServer/Views/Stores/PayButton.cshtml +++ b/BTCPayServer/Views/Stores/PayButton.cshtml @@ -122,25 +122,6 @@
-

-
-

Disable Pay Button

-
-
-
-
-

- Disabling this feature will cause your currently used Pay Buttons to stop working. - Customers trying to use Pay Button to create Invoices will be displayed appropriate message. - You can always reenable Pay Buttons at later time. -

- @Html.Hidden("EnableStore", false) - -
-
-
-
- @section HeadScripts { diff --git a/BTCPayServer/Views/Stores/PayButtonEnable.cshtml b/BTCPayServer/Views/Stores/PayButtonEnable.cshtml index bfd326248..ab59924ab 100644 --- a/BTCPayServer/Views/Stores/PayButtonEnable.cshtml +++ b/BTCPayServer/Views/Stores/PayButtonEnable.cshtml @@ -1,6 +1,6 @@ @{ Layout = "../Shared/_NavLayout.cshtml"; - ViewData.SetActivePageAndTitle(StoreNavPages.PayButton, "Please confirm you want to enable Pay Button"); + ViewData.SetActivePageAndTitle(StoreNavPages.PayButton, "Please confirm you want to allow anyone to create invoices in your store"); ViewBag.MainTitle = "Pay Button"; } @@ -12,10 +12,10 @@

To start using Pay Buttons you need to explicitly turn on this feature. - Once you do so, valid POST requests from any source will allow creation of Invoices on your instance of BtcPayServer. + Once you do so, any source will be able to create an invoice on your instance store.

@Html.Hidden("EnableStore", true) - +
diff --git a/BTCPayServer/Views/Stores/UpdateStore.cshtml b/BTCPayServer/Views/Stores/UpdateStore.cshtml index 68f715c37..912806b60 100644 --- a/BTCPayServer/Views/Stores/UpdateStore.cshtml +++ b/BTCPayServer/Views/Stores/UpdateStore.cshtml @@ -46,6 +46,10 @@ +
+ + +
From 0d3364b3dadfc4a2af65b472b40d1e95587d1e8a Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 8 Sep 2018 14:53:11 +0900 Subject: [PATCH 12/12] Change button path to api/v1/invoices --- BTCPayServer/Controllers/PublicController.cs | 11 +++++----- .../Filters/OnlyMediaTypeAttribute.cs | 22 +++++++++++++++++++ .../StoreViewModels/PayButtonViewModel.cs | 1 + BTCPayServer/wwwroot/paybutton/paybutton.js | 3 ++- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/BTCPayServer/Controllers/PublicController.cs b/BTCPayServer/Controllers/PublicController.cs index bec4d906b..c657138b9 100644 --- a/BTCPayServer/Controllers/PublicController.cs +++ b/BTCPayServer/Controllers/PublicController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Filters; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Cors; @@ -22,12 +23,13 @@ namespace BTCPayServer.Controllers private StoreRepository _StoreRepository; [HttpPost] - [Route("/pay/{storeId}")] + [Route("api/v1/invoices")] + [MediaTypeAcceptConstraintAttribute("text/html")] [IgnoreAntiforgeryToken] [EnableCors(CorsPolicies.All)] - public async Task PayButtonHandle(string storeId, [FromForm]PayButtonViewModel model) + public async Task PayButtonHandle([FromForm]PayButtonViewModel model) { - var store = await _StoreRepository.FindStore(storeId); + var store = await _StoreRepository.FindStore(model.StoreId); if (store == null) ModelState.AddModelError("Store", "Invalid store"); else @@ -36,8 +38,7 @@ namespace BTCPayServer.Controllers if (!storeBlob.AnyoneCanInvoice) ModelState.AddModelError("Store", "Store has not enabled Pay Button"); } - - // TODO: extract validation to model + if (model == null || model.Price <= 0) ModelState.AddModelError("Price", "Price must be greater than 0"); diff --git a/BTCPayServer/Filters/OnlyMediaTypeAttribute.cs b/BTCPayServer/Filters/OnlyMediaTypeAttribute.cs index afb9e597a..3353c0195 100644 --- a/BTCPayServer/Filters/OnlyMediaTypeAttribute.cs +++ b/BTCPayServer/Filters/OnlyMediaTypeAttribute.cs @@ -28,6 +28,28 @@ namespace BTCPayServer.Filters } } + public class MediaTypeAcceptConstraintAttribute : Attribute, IActionConstraint + { + public MediaTypeAcceptConstraintAttribute(string mediaType) + { + MediaType = mediaType ?? throw new ArgumentNullException(nameof(mediaType)); + } + + public string MediaType + { + get; set; + } + + public int Order => 100; + + public bool Accept(ActionConstraintContext context) + { + if (!context.RouteContext.HttpContext.Request.Headers.ContainsKey("Accept")) + return false; + return context.RouteContext.HttpContext.Request.Headers["Accept"].ToString().StartsWith(MediaType, StringComparison.Ordinal); + } + } + public class BitpayAPIConstraintAttribute : Attribute, IActionConstraint { public BitpayAPIConstraintAttribute(bool isBitpayAPI = true) diff --git a/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs b/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs index b8fbd306c..ccb388e39 100644 --- a/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs @@ -9,6 +9,7 @@ namespace BTCPayServer.Models.StoreViewModels public class PayButtonViewModel { public decimal Price { get; set; } + public string InvoiceId { get; set; } [Required] public string Currency { get; set; } public string CheckoutDesc { get; set; } diff --git a/BTCPayServer/wwwroot/paybutton/paybutton.js b/BTCPayServer/wwwroot/paybutton/paybutton.js index ba544e882..a901a14fc 100644 --- a/BTCPayServer/wwwroot/paybutton/paybutton.js +++ b/BTCPayServer/wwwroot/paybutton/paybutton.js @@ -42,7 +42,8 @@ function inputChanges(event, buttonSize) { srvModel.buttonSize = buttonSize; } - var html = '
'; + var html = ''; + html += addinput("storeId", srvModel.storeId); html += addinput("price", srvModel.price); if (srvModel.currency) { html += addinput("currency", srvModel.currency);