From 5f8407b4b1db30cc0208661506ad8d84aeb6abec Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Mon, 23 Oct 2017 19:27:22 +0900 Subject: [PATCH] Allow the merchant to disable network fees at store level --- BTCPayServer/Controllers/InvoiceController.cs | 2 +- BTCPayServer/Controllers/StoresController.cs | 9 + BTCPayServer/Data/StoreData.cs | 26 + .../20171023101754_StoreBlob.Designer.cs | 452 ++++++++++++++++++ .../Migrations/20171023101754_StoreBlob.cs | 24 + .../ApplicationDbContextModelSnapshot.cs | 2 + .../Models/StoreViewModels/StoreViewModel.cs | 7 + BTCPayServer/Views/Stores/UpdateStore.cshtml | 4 + 8 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 BTCPayServer/Migrations/20171023101754_StoreBlob.Designer.cs create mode 100644 BTCPayServer/Migrations/20171023101754_StoreBlob.cs diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index 023b2e654..efb7d66ab 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -107,7 +107,7 @@ namespace BTCPayServer.Controllers entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); - entity.TxFee = (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes + entity.TxFee = store.GetStoreBlob(_Network).NetworkFeeDisabled ? Money.Zero : (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes entity.Rate = (double)await _RateProvider.GetRateAsync(invoice.Currency); entity.PosData = invoice.PosData; entity.DepositAddress = await _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy)); diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 31d15eb08..353e190f4 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -119,6 +119,7 @@ namespace BTCPayServer.Controllers var vm = new StoreViewModel(); vm.StoreName = store.StoreName; vm.StoreWebsite = store.StoreWebsite; + vm.NetworkFee = !store.GetStoreBlob(_Network).NetworkFeeDisabled; vm.SpeedPolicy = store.SpeedPolicy; vm.DerivationScheme = store.DerivationStrategy; vm.StatusMessage = StatusMessage; @@ -173,6 +174,14 @@ namespace BTCPayServer.Controllers } } + if(store.GetStoreBlob(_Network).NetworkFeeDisabled != !model.NetworkFee) + { + var blob = store.GetStoreBlob(_Network); + blob.NetworkFeeDisabled = !model.NetworkFee; + store.SetStoreBlob(blob, _Network); + needUpdate = true; + } + if(needUpdate) { await _Repo.UpdateStore(store); diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index 8c3a6e401..c834c0c29 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -1,9 +1,12 @@ using BTCPayServer.Models; using BTCPayServer.Services.Invoices; +using NBitcoin; +using NBXplorer; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; +using System.Text; using System.Threading.Tasks; namespace BTCPayServer.Data @@ -51,5 +54,28 @@ namespace BTCPayServer.Data { get; set; } + public byte[] StoreBlob + { + get; + set; + } + + public StoreBlob GetStoreBlob(Network network) + { + return StoreBlob == null ? new StoreBlob() : new Serializer(network).ToObject(Encoding.UTF8.GetString(StoreBlob)); + } + + public void SetStoreBlob(StoreBlob storeBlob, Network network) + { + StoreBlob = Encoding.UTF8.GetBytes(new Serializer(network).ToString(storeBlob)); + } + } + + public class StoreBlob + { + public bool NetworkFeeDisabled + { + get; set; + } } } diff --git a/BTCPayServer/Migrations/20171023101754_StoreBlob.Designer.cs b/BTCPayServer/Migrations/20171023101754_StoreBlob.Designer.cs new file mode 100644 index 000000000..3d99de140 --- /dev/null +++ b/BTCPayServer/Migrations/20171023101754_StoreBlob.Designer.cs @@ -0,0 +1,452 @@ +// +using BTCPayServer.Data; +using BTCPayServer.Services.Invoices; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using System; + +namespace BTCPayServer.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20171023101754_StoreBlob")] + partial class StoreBlob + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b => + { + b.Property("Address") + .ValueGeneratedOnAdd(); + + b.Property("InvoiceDataId"); + + b.HasKey("Address"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("AddressInvoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Blob"); + + b.Property("Created"); + + b.Property("CustomerEmail"); + + b.Property("ExceptionStatus"); + + b.Property("ItemCode"); + + b.Property("OrderId"); + + b.Property("Status"); + + b.Property("StoreDataId"); + + b.HasKey("Id"); + + b.HasIndex("StoreDataId"); + + b.ToTable("Invoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Facade"); + + b.Property("Label"); + + b.Property("PairingTime"); + + b.Property("SIN"); + + b.Property("StoreDataId"); + + b.HasKey("Id"); + + b.HasIndex("SIN"); + + b.HasIndex("StoreDataId"); + + b.ToTable("PairedSINData"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateCreated"); + + b.Property("Expiration"); + + b.Property("Facade"); + + b.Property("Label"); + + b.Property("SIN"); + + b.Property("StoreDataId"); + + b.Property("TokenValue"); + + b.HasKey("Id"); + + b.ToTable("PairingCodes"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PaymentData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Blob"); + + b.Property("InvoiceDataId"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("Payments"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.HasKey("Id"); + + b.ToTable("PendingInvoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Blob"); + + b.Property("InvoiceDataId"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("RefundAddresses"); + }); + + modelBuilder.Entity("BTCPayServer.Data.SettingData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("BTCPayServer.Data.StoreData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DerivationStrategy"); + + b.Property("SpeedPolicy"); + + b.Property("StoreBlob"); + + b.Property("StoreCertificate"); + + b.Property("StoreName"); + + b.Property("StoreWebsite"); + + b.HasKey("Id"); + + b.ToTable("Stores"); + }); + + modelBuilder.Entity("BTCPayServer.Data.UserStore", b => + { + b.Property("ApplicationUserId"); + + b.Property("StoreDataId"); + + b.Property("Role"); + + b.HasKey("ApplicationUserId", "StoreDataId"); + + b.HasIndex("StoreDataId"); + + b.ToTable("UserStore"); + }); + + modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("RequiresEmailConfirmation"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany() + .HasForeignKey("InvoiceDataId"); + }); + + modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b => + { + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany() + .HasForeignKey("StoreDataId"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PaymentData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("Payments") + .HasForeignKey("InvoiceDataId"); + }); + + modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("RefundAddresses") + .HasForeignKey("InvoiceDataId"); + }); + + modelBuilder.Entity("BTCPayServer.Data.UserStore", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser") + .WithMany("UserStores") + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("UserStores") + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BTCPayServer/Migrations/20171023101754_StoreBlob.cs b/BTCPayServer/Migrations/20171023101754_StoreBlob.cs new file mode 100644 index 000000000..01a8299fe --- /dev/null +++ b/BTCPayServer/Migrations/20171023101754_StoreBlob.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace BTCPayServer.Migrations +{ + public partial class StoreBlob : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "StoreBlob", + table: "Stores", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "StoreBlob", + table: "Stores"); + } + } +} diff --git a/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs b/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs index 65d4da025..9054cb5cb 100644 --- a/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs @@ -173,6 +173,8 @@ namespace BTCPayServer.Migrations b.Property("SpeedPolicy"); + b.Property("StoreBlob"); + b.Property("StoreCertificate"); b.Property("StoreName"); diff --git a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs index e2cafc12d..203b75dc7 100644 --- a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs @@ -21,6 +21,7 @@ namespace BTCPayServer.Models.StoreViewModels [Url] [Display(Name = "Store Website")] + [MaxLength(500)] public string StoreWebsite { get; @@ -39,6 +40,12 @@ namespace BTCPayServer.Models.StoreViewModels get; set; } + [Display(Name = "Add network fee to invoice (vary with mining fees)")] + public bool NetworkFee + { + get; set; + } + public List<(string KeyPath, string Address)> AddressSamples { get; set; diff --git a/BTCPayServer/Views/Stores/UpdateStore.cshtml b/BTCPayServer/Views/Stores/UpdateStore.cshtml index c80c329d0..f4548901c 100644 --- a/BTCPayServer/Views/Stores/UpdateStore.cshtml +++ b/BTCPayServer/Views/Stores/UpdateStore.cshtml @@ -26,6 +26,10 @@ +
+ + +