From ec68d2a0e67421a433e7b256b2d19e19b0785b8a Mon Sep 17 00:00:00 2001 From: Samuel Adams Date: Fri, 26 Nov 2021 16:13:41 +0200 Subject: [PATCH] Remove Confirmed state in UI (#3090) * Remove Confirmed state in UI Closes #1789. * Add infobox & improve refund tooltip * Update BTCPayServer/Views/Invoice/ListInvoices.cshtml Add @dennisreimann suggestion Co-authored-by: d11n * Add "don't show again" button Adds a "Don't Show Again" button to the infobox. Also a bugfix that was preventing the new status from showing in the invoice details page. * Add User blob and move invoice status notice to it Co-authored-by: d11n Co-authored-by: Kukks --- BTCPayServer.Data/Data/ApplicationUser.cs | 2 + .../Migrations/20211125081400_AddUserBlob.cs | 29 ++++++++ .../ApplicationDbContextModelSnapshot.cs | 3 + .../Controllers/InvoiceController.UI.cs | 11 ++- BTCPayServer/Controllers/ManageController.cs | 20 ++++++ BTCPayServer/Extensions/UserExtensions.cs | 33 +++++++++ BTCPayServer/Hosting/MigrationStartupTask.cs | 16 +++++ .../InvoicingModels/InvoiceDetailsModel.cs | 4 +- .../Models/InvoicingModels/InvoicesModel.cs | 4 +- BTCPayServer/Services/MigrationSettings.cs | 1 + .../Views/Apps/UpdatePointOfSale.cshtml | 2 +- BTCPayServer/Views/Invoice/Invoice.cshtml | 21 ++++-- .../Invoice/InvoiceStatusChangePartial.cshtml | 57 +++++++++++++++ .../Views/Invoice/ListInvoices.cshtml | 72 ++++++++++--------- 14 files changed, 225 insertions(+), 50 deletions(-) create mode 100644 BTCPayServer.Data/Migrations/20211125081400_AddUserBlob.cs create mode 100644 BTCPayServer/Extensions/UserExtensions.cs create mode 100644 BTCPayServer/Views/Invoice/InvoiceStatusChangePartial.cshtml diff --git a/BTCPayServer.Data/Data/ApplicationUser.cs b/BTCPayServer.Data/Data/ApplicationUser.cs index 07342221b..5cd889d56 100644 --- a/BTCPayServer.Data/Data/ApplicationUser.cs +++ b/BTCPayServer.Data/Data/ApplicationUser.cs @@ -19,6 +19,8 @@ namespace BTCPayServer.Data public List Notifications { get; set; } public List UserStores { get; set; } public List Fido2Credentials { get; set; } + + public byte[] Blob { get; set; } public List> UserRoles { get; set; } diff --git a/BTCPayServer.Data/Migrations/20211125081400_AddUserBlob.cs b/BTCPayServer.Data/Migrations/20211125081400_AddUserBlob.cs new file mode 100644 index 000000000..dccd38850 --- /dev/null +++ b/BTCPayServer.Data/Migrations/20211125081400_AddUserBlob.cs @@ -0,0 +1,29 @@ +using System; +using BTCPayServer.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +namespace BTCPayServer.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20211125081400_AddUserBlob")] + public partial class AddUserBlob : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Blob", + table: "AspNetUsers", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Blob", + table: "AspNetUsers"); + } + } +} diff --git a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs index 13d9a20af..60f6face0 100644 --- a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -105,6 +105,9 @@ namespace BTCPayServer.Migrations b.Property("AccessFailedCount") .HasColumnType("INTEGER"); + b.Property("Blob") + .HasColumnType("BLOB"); + b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnType("TEXT"); diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 39365ea53..baa5cd02e 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -104,7 +104,7 @@ namespace BTCPayServer.Controllers StoreLink = Url.Action(nameof(StoresController.PaymentMethods), "Stores", new { storeId = store.Id }), PaymentRequestLink = Url.Action(nameof(PaymentRequestController.ViewPaymentRequest), "PaymentRequest", new { id = invoice.Metadata.PaymentRequestId }), Id = invoice.Id, - State = invoiceState.ToString(), + State = invoiceState.Status.ToModernStatus().ToString(), TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" : invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" : invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" : @@ -128,7 +128,7 @@ namespace BTCPayServer.Controllers .Select(c => new Models.StoreViewModels.DeliveryViewModel(c)) .ToList(), CanMarkInvalid = invoiceState.CanMarkInvalid(), - CanMarkComplete = invoiceState.CanMarkComplete(), + CanMarkSettled = invoiceState.CanMarkComplete(), }; model.Addresses = invoice.HistoricalAddresses.Select(h => new InvoiceDetailsModel.AddressModel @@ -763,7 +763,7 @@ namespace BTCPayServer.Controllers RedirectUrl = invoice.RedirectURL?.AbsoluteUri ?? string.Empty, AmountCurrency = _CurrencyNameTable.DisplayFormatCurrency(invoice.Price, invoice.Currency), CanMarkInvalid = state.CanMarkInvalid(), - CanMarkComplete = state.CanMarkComplete(), + CanMarkSettled = state.CanMarkComplete(), Details = InvoicePopulatePayments(invoice), }); } @@ -936,10 +936,10 @@ namespace BTCPayServer.Controllers await _InvoiceRepository.MarkInvoiceStatus(invoiceId, InvoiceStatus.Invalid); model.StatusString = new InvoiceState("invalid", "marked").ToString(); } - else if (newState == "complete") + else if (newState == "settled") { await _InvoiceRepository.MarkInvoiceStatus(invoiceId, InvoiceStatus.Settled); - model.StatusString = new InvoiceState("complete", "marked").ToString(); + model.StatusString = new InvoiceState("settled", "marked").ToString(); } return Json(model); @@ -999,6 +999,5 @@ namespace BTCPayServer.Controllers return result; } } - } } diff --git a/BTCPayServer/Controllers/ManageController.cs b/BTCPayServer/Controllers/ManageController.cs index 44a2eabc4..990383c46 100644 --- a/BTCPayServer/Controllers/ManageController.cs +++ b/BTCPayServer/Controllers/ManageController.cs @@ -85,6 +85,26 @@ namespace BTCPayServer.Controllers return View(model); } + [HttpPost] + [ValidateAntiForgeryToken] + public async Task DisableShowInvoiceStatusChangeHint() + { + + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var blob = user.GetBlob(); + blob.ShowInvoiceStatusChangeHint = false; + if (user.SetBlob(blob)) + { + await _userManager.UpdateAsync(user); + } + return RedirectToAction(nameof(Index)); + } + [HttpPost] [ValidateAntiForgeryToken] public async Task Index(IndexViewModel model) diff --git a/BTCPayServer/Extensions/UserExtensions.cs b/BTCPayServer/Extensions/UserExtensions.cs new file mode 100644 index 000000000..aceb8f171 --- /dev/null +++ b/BTCPayServer/Extensions/UserExtensions.cs @@ -0,0 +1,33 @@ +using System.Linq; +using BTCPayServer.Data; +using BTCPayServer.Services.Invoices; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer +{ + public static class UserExtensions + { + public static UserBlob GetBlob(this ApplicationUser user) + { + var result = user.Blob == null + ? new UserBlob() + : JObject.Parse(ZipUtils.Unzip(user.Blob)).ToObject(); + return result; + } + public static bool SetBlob(this ApplicationUser user, UserBlob blob) + { + var newBytes = InvoiceRepository.ToBytes(blob); + if (user.Blob != null && newBytes.SequenceEqual(user.Blob)) + { + return false; + } + user.Blob = newBytes; + return true; + } + } + + public class UserBlob + { + public bool ShowInvoiceStatusChangeHint { get; set; } + } +} diff --git a/BTCPayServer/Hosting/MigrationStartupTask.cs b/BTCPayServer/Hosting/MigrationStartupTask.cs index 37aaed634..6f76fe9fe 100644 --- a/BTCPayServer/Hosting/MigrationStartupTask.cs +++ b/BTCPayServer/Hosting/MigrationStartupTask.cs @@ -163,6 +163,12 @@ namespace BTCPayServer.Hosting settings.MigratePayoutDestinationId = true; await _Settings.UpdateSetting(settings); } + if (!settings.AddInitialUserBlob) + { + await AddInitialUserBlob(); + settings.AddInitialUserBlob = true; + await _Settings.UpdateSetting(settings); + } } catch (Exception ex) { @@ -171,6 +177,16 @@ namespace BTCPayServer.Hosting } } + private async Task AddInitialUserBlob() + { + await using var ctx = _DBContextFactory.CreateContext(); + foreach (var user in await ctx.Users.AsQueryable().ToArrayAsync()) + { + user.SetBlob(new UserBlob() { ShowInvoiceStatusChangeHint = true }); + } + await ctx.SaveChangesAsync(); + } + private async Task MigratePayoutDestinationId() { await using var ctx = _DBContextFactory.CreateContext(); diff --git a/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs b/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs index a82e90a9a..878827d69 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs @@ -129,8 +129,8 @@ namespace BTCPayServer.Models.InvoicingModels public bool Archived { get; set; } public bool CanRefund { get; set; } public bool ShowCheckout { get; set; } - public bool CanMarkComplete { get; set; } + public bool CanMarkSettled { get; set; } public bool CanMarkInvalid { get; set; } - public bool CanMarkStatus => CanMarkComplete || CanMarkInvalid; + public bool CanMarkStatus => CanMarkSettled || CanMarkInvalid; } } diff --git a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs index af6b69ea3..35cbbbe70 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs @@ -20,9 +20,9 @@ namespace BTCPayServer.Models.InvoicingModels public string InvoiceId { get; set; } public InvoiceState Status { get; set; } - public bool CanMarkComplete { get; set; } + public bool CanMarkSettled { get; set; } public bool CanMarkInvalid { get; set; } - public bool CanMarkStatus => CanMarkComplete || CanMarkInvalid; + public bool CanMarkStatus => CanMarkSettled || CanMarkInvalid; public bool ShowCheckout { get; set; } public string ExceptionStatus { get; set; } public string AmountCurrency { get; set; } diff --git a/BTCPayServer/Services/MigrationSettings.cs b/BTCPayServer/Services/MigrationSettings.cs index 1b69c735d..38b7b2ecb 100644 --- a/BTCPayServer/Services/MigrationSettings.cs +++ b/BTCPayServer/Services/MigrationSettings.cs @@ -27,5 +27,6 @@ namespace BTCPayServer.Services public int? MigratedInvoiceTextSearchPages { get; set; } public bool MigrateAppCustomOption { get; set; } public bool MigratePayoutDestinationId { get; set; } + public bool AddInitialUserBlob { get; set; } } } diff --git a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml index 520cf0301..f81a979d5 100644 --- a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml @@ -200,7 +200,7 @@

  • Send a GET request to https://btcpay.example.com/invoices/{invoiceId} with Content-Type: application/json; Authorization: Basic YourLegacyAPIkey", Legacy API key can be created with Access Tokens in Store settings
  • -
  • Verify that the orderId is from your backend, that the price is correct and that status is either confirmed or complete
  • +
  • Verify that the orderId is from your backend, that the price is correct and that status is settled
  • You can then ship your order

diff --git a/BTCPayServer/Views/Invoice/Invoice.cshtml b/BTCPayServer/Views/Invoice/Invoice.cshtml index 0119e3a0f..fed06c486 100644 --- a/BTCPayServer/Views/Invoice/Invoice.cshtml +++ b/BTCPayServer/Views/Invoice/Invoice.cshtml @@ -1,3 +1,4 @@ +@using BTCPayServer.Client.Models @model InvoiceDetailsModel @{ ViewData["Title"] = $"Invoice {Model.Id}"; @@ -20,7 +21,7 @@ $.post(invoiceId + "/changestate/" + newState) .done(function (data) { var alertClassModifier = { - "complete (marked)": 'success', + "settled (marked)": 'success', "invalid (marked)": 'danger' }[data.statusString]; var statusHtml = "" + data.statusString + " " @@ -63,7 +64,7 @@ } else { - + }
@@ -147,6 +152,10 @@ else { @Model.State + @if (Model.StatusException != InvoiceExceptionStatus.None) + { + @String.Format(" ({0})", Model.StatusException.ToString()); + } } @@ -384,4 +393,4 @@ - \ No newline at end of file + diff --git a/BTCPayServer/Views/Invoice/InvoiceStatusChangePartial.cshtml b/BTCPayServer/Views/Invoice/InvoiceStatusChangePartial.cshtml new file mode 100644 index 000000000..c1797d0ac --- /dev/null +++ b/BTCPayServer/Views/Invoice/InvoiceStatusChangePartial.cshtml @@ -0,0 +1,57 @@ +@inject UserManager _userManager + +@* This is a temporary infobox to inform users about the state changes in 1.4.0. It should be removed eventually. *@ +@if ((await _userManager.GetUserAsync(User)).GetBlob().ShowInvoiceStatusChangeHint) +{ +
+ +
+} + + diff --git a/BTCPayServer/Views/Invoice/ListInvoices.cshtml b/BTCPayServer/Views/Invoice/ListInvoices.cshtml index 50740c716..2b8a8cae7 100644 --- a/BTCPayServer/Views/Invoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/Invoice/ListInvoices.cshtml @@ -1,3 +1,4 @@ +@using BTCPayServer.Client.Models @model InvoicesModel @{ ViewData.SetActivePageAndTitle(InvoiceNavPages.Index, "Invoices"); @@ -58,14 +59,13 @@ background: #c94a47; color: #fff; } - - .badge-confirmed, - .badge-paid { + + .badge-processing { background: #f1c332; color: #000; } - - .badge-complete { + + .badge-settled { background: #329f80; color: #fff; } @@ -182,22 +182,23 @@
-
-

- @ViewData["Title"] - - - - - -

- - - Create an invoice - -
- -
+
+

+ @ViewData["Title"] + + + + + +

+ + + Create an invoice + +
+ + +
@@ -216,7 +217,7 @@
@@ -381,9 +379,13 @@ } @if (invoice.CanMarkStatus) { -
+
@@ -403,8 +405,12 @@ } else { - - @invoice.Status.ToString().ToLower() + + @invoice.Status.Status.ToModernStatus().ToString() @* @invoice.Status.ToString().ToLower() *@ + @if (invoice.Status.ExceptionStatus != InvoiceExceptionStatus.None) + { + @String.Format("({0})", @invoice.Status.ExceptionStatus.ToString()); + } } @foreach (var paymentType in invoice.Details.Payments.Select(payment => payment.GetPaymentMethodId()?.PaymentType).Distinct().Where(type => type != null && !string.IsNullOrEmpty(type.GetBadge())))