From 002f6d3e87a5557d3cf12f8d24b59fca32728ab9 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 29 Jul 2020 19:11:54 +0900 Subject: [PATCH] Isolate altcoins tests in their own folder --- BTCPayServer.Tests/AltcoinTests.cs | 11 - .../AltcoinTests/AltcoinTests.cs | 857 ++++++++++++++++++ .../AltcoinTests/ElementsTests.cs | 1 + BTCPayServer.Tests/CheckoutUITests.cs | 47 - BTCPayServer.Tests/SeleniumTests.cs | 75 +- BTCPayServer.Tests/ServerTester.cs | 11 +- BTCPayServer.Tests/UnitTest1.cs | 669 -------------- .../Services/BTCPayServerEnvironment.cs | 3 + 8 files changed, 868 insertions(+), 806 deletions(-) delete mode 100644 BTCPayServer.Tests/AltcoinTests.cs create mode 100644 BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs diff --git a/BTCPayServer.Tests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests.cs deleted file mode 100644 index af974acc0..000000000 --- a/BTCPayServer.Tests/AltcoinTests.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace BTCPayServer.Tests -{ - public class AltcoinTests - { - } -} diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs new file mode 100644 index 000000000..d8bee3b08 --- /dev/null +++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs @@ -0,0 +1,857 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Client; +using BTCPayServer.Client.Models; +using BTCPayServer.Configuration; +using BTCPayServer.Controllers; +using BTCPayServer.Data; +using BTCPayServer.Events; +using BTCPayServer.HostedServices; +using BTCPayServer.Lightning; +using BTCPayServer.Models; +using BTCPayServer.Models.AccountViewModels; +using BTCPayServer.Models.AppViewModels; +using BTCPayServer.Models.InvoicingModels; +using BTCPayServer.Models.ServerViewModels; +using BTCPayServer.Models.StoreViewModels; +using BTCPayServer.Models.WalletViewModels; +using BTCPayServer.Payments; +using BTCPayServer.Payments.Bitcoin; +using BTCPayServer.Payments.Lightning; +using BTCPayServer.Rating; +using BTCPayServer.Security.Bitpay; +using BTCPayServer.Services; +using BTCPayServer.Services.Apps; +using BTCPayServer.Services.Invoices; +using BTCPayServer.Services.Rates; +using BTCPayServer.Tests.Logging; +using BTCPayServer.U2F.Models; +using BTCPayServer.Validation; +using ExchangeSharp; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NBitcoin; +using NBitcoin.DataEncoders; +using NBitcoin.Payment; +using NBitpayClient; +using NBXplorer.DerivationStrategy; +using NBXplorer.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; +using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel; + +namespace BTCPayServer.Tests +{ + public class AltcoinTests + { + public const int TestTimeout = 60_000; + + [Fact] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + [Trait("Lightning", "Lightning")] + public async Task CanAddDerivationSchemes() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLTC(); + tester.ActivateLightning(); + await tester.StartAsync(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + user.RegisterDerivationScheme("LTC"); + user.RegisterLightningNode("BTC", LightningConnectionType.CLightning); + var btcNetwork = tester.PayTester.Networks.GetNetwork("BTC"); + var invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 1.5m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + Assert.Equal(3, invoice.CryptoInfo.Length); + + var controller = user.GetController(); + var lightningVM = + (LightningNodeViewModel)Assert.IsType(controller.AddLightningNode(user.StoreId, "BTC")) + .Model; + Assert.True(lightningVM.Enabled); + lightningVM.Enabled = false; + controller.AddLightningNode(user.StoreId, lightningVM, "save", "BTC").GetAwaiter().GetResult(); + lightningVM = + (LightningNodeViewModel)Assert.IsType(controller.AddLightningNode(user.StoreId, "BTC")) + .Model; + Assert.False(lightningVM.Enabled); + + // Only Enabling/Disabling the payment method must redirect to store page + var derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + Assert.True(derivationVM.Enabled); + derivationVM.Enabled = false; + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + Assert.False(derivationVM.Enabled); + + // Clicking next without changing anything should send to the confirmation screen + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + + invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 1.5m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + Assert.Single(invoice.CryptoInfo); + Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode); + + // Removing the derivation scheme, should redirect to store page + var oldScheme = derivationVM.DerivationScheme; + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.DerivationScheme = null; + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + + // Setting it again should redirect to the confirmation page + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.DerivationScheme = oldScheme; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + + + //cobo vault file + var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}"; + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.WalletFile = TestUtils.GetFormFile("wallet3.json", content); + derivationVM.Enabled = true; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + + //wasabi wallet file + content = + "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}"; + + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.WalletFile = TestUtils.GetFormFile("wallet4.json", content); + derivationVM.Enabled = true; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + + + // Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network) + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + content = + "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; + derivationVM.WalletFile = TestUtils.GetFormFile("wallet.json", content); + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.False(derivationVM + .Confirmation); // Should fail, we are giving a mainnet file to a testnet network + + // And with a good file? (upub) + content = + "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.WalletFile = TestUtils.GetFormFile("wallet2.json", content); + derivationVM.Enabled = true; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + + + // Now let's check that no data has been lost in the process + var store = tester.PayTester.StoreRepository.FindStore(user.StoreId).GetAwaiter().GetResult(); + var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks) +#pragma warning disable CS0618 // Type or member is obsolete + .OfType().First(o => o.PaymentId.IsBTCOnChain); +#pragma warning restore CS0618 // Type or member is obsolete + DerivationSchemeSettings.TryParseFromWalletFile(content, onchainBTC.Network, out var expected); + Assert.Equal(expected.ToJson(), onchainBTC.ToJson()); + + // Let's check that the root hdkey and account key path are taken into account when making a PSBT + invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 1.5m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + tester.ExplorerNode.Generate(1); + var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == "BTC").Address, + tester.ExplorerNode.Network); + tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(1m)); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal("paid", invoice.Status); + }); + var wallet = tester.PayTester.GetController(); + var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC, + new WalletSendModel() + { + Outputs = new List() + { + new WalletSendModel.TransactionOutput() + { + Amount = 0.5m, + DestinationAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, btcNetwork.NBitcoinNetwork) + .ToString(), + } + }, + FeeSatoshiPerByte = 1 + }, default).GetAwaiter().GetResult(); + + Assert.NotNull(psbt); + + var root = new Mnemonic( + "usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage") + .DeriveExtKey().AsHDKeyCache(); + var account = root.Derive(new KeyPath("m/49'/0'/0'")); + Assert.All(psbt.PSBT.Inputs, input => + { + var keyPath = input.HDKeyPaths.Single(); + Assert.False(keyPath.Value.KeyPath.IsHardened); + Assert.Equal(account.Derive(keyPath.Value.KeyPath).GetPublicKey(), keyPath.Key); + Assert.Equal(keyPath.Value.MasterFingerprint, + onchainBTC.AccountKeySettings[0].AccountKey.GetPublicKey().GetHDFingerPrint()); + }); + } + } + + [Fact(Timeout = TestTimeout)] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + [Trait("Lightning", "Lightning")] + public async Task CanCreateInvoiceWithSpecificPaymentMethods() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLightning(); + tester.ActivateLTC(); + await tester.StartAsync(); + await tester.EnsureChannelsSetup(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterLightningNode("BTC", LightningConnectionType.Charge); + user.RegisterDerivationScheme("BTC"); + user.RegisterDerivationScheme("LTC"); + + var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC")); + Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count); + + + invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC") + { + SupportedTransactionCurrencies = new Dictionary() + { + {"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}} + } + }); + + Assert.Single(invoice.SupportedTransactionCurrencies); + } + } + + [Fact(Timeout = TestTimeout)] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + public async Task CanHaveLTCOnlyStore() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLTC(); + await tester.StartAsync(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("LTC"); + + // First we try payment with a merchant having only BTC + var invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 500, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + Assert.Single(invoice.CryptoInfo); + Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode); + Assert.True(invoice.PaymentCodes.ContainsKey("LTC")); + Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC")); + Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); + Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); + Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); + var cashCow = tester.LTCExplorerNode; + var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); + var firstPayment = Money.Coins(0.1m); + cashCow.SendToAddress(invoiceAddress, firstPayment); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid); + }); + + Assert.Single(invoice.CryptoInfo); // Only BTC should be presented + + var controller = tester.PayTester.GetController(null); + var checkout = + (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) + .GetAwaiter().GetResult()).Value; + Assert.Single(checkout.AvailableCryptos); + Assert.Equal("LTC", checkout.CryptoCode); + + ////////////////////// + + // Despite it is called BitcoinAddress it should be LTC because BTC is not available + Assert.Null(invoice.BitcoinAddress); + Assert.NotEqual(1.0m, invoice.Rate); + Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate + cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due); + + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal("paid", invoice.Status); + checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) + .GetAwaiter().GetResult()).Value; + Assert.Equal("paid", checkout.Status); + }); + } + } + + + [Fact] + [Trait("Selenium", "Selenium")] + [Trait("Altcoins", "Altcoins")] + public async Task CanCreateRefunds() + { + using (var s = SeleniumTester.Create()) + { + s.Server.ActivateLTC(); + await s.StartAsync(); + var user = s.Server.NewAccount(); + await user.GrantAccessAsync(); + s.GoToLogin(); + s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); + user.RegisterDerivationScheme("BTC"); + await s.Server.ExplorerNode.GenerateAsync(1); + + foreach (var multiCurrency in new[] { false, true }) + { + if (multiCurrency) + user.RegisterDerivationScheme("LTC"); + foreach (var rateSelection in new[] { "FiatText", "CurrentRateText", "RateThenText" }) + await CanCreateRefundsCore(s, user, multiCurrency, rateSelection); + } + } + } + + private static async Task CanCreateRefundsCore(SeleniumTester s, TestAccount user, bool multiCurrency, string rateSelection) + { + s.GoToHome(); + s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m, 5100.0m)); + var invoice = await user.BitPay.CreateInvoiceAsync(new NBitpayClient.Invoice() + { + Currency = "USD", + Price = 5000.0m + }); + var info = invoice.CryptoInfo.First(o => o.CryptoCode == "BTC"); + var totalDue = decimal.Parse(info.TotalDue, CultureInfo.InvariantCulture); + var paid = totalDue + 0.1m; + await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(info.Address, Network.RegTest), Money.Coins(paid)); + await s.Server.ExplorerNode.GenerateAsync(1); + await TestUtils.EventuallyAsync(async () => + { + invoice = await user.BitPay.GetInvoiceAsync(invoice.Id); + Assert.Equal("confirmed", invoice.Status); + }); + + // BTC crash by 50% + s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m / 2.0m, 5100.0m / 2.0m)); + s.GoToInvoice(invoice.Id); + s.Driver.FindElement(By.Id("refundlink")).Click(); + if (multiCurrency) + { + s.Driver.FindElement(By.Id("SelectedPaymentMethod")).SendKeys("BTC" + Keys.Enter); + s.Driver.FindElement(By.Id("ok")).Click(); + } + Assert.Contains("$5,500.00", s.Driver.PageSource); // Should propose reimburse in fiat + Assert.Contains("1.10000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the rate of before + Assert.Contains("2.20000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the current rate + s.Driver.FindElement(By.Id(rateSelection)).Click(); + s.Driver.FindElement(By.Id("ok")).Click(); + Assert.Contains("pull-payments", s.Driver.Url); + if (rateSelection == "FiatText") + Assert.Contains("$5,500.00", s.Driver.PageSource); + if (rateSelection == "CurrentRateText") + Assert.Contains("2.20000000 ₿", s.Driver.PageSource); + if (rateSelection == "RateThenText") + Assert.Contains("1.10000000 ₿", s.Driver.PageSource); + s.GoToHome(); + s.GoToInvoices(); + s.GoToInvoice(invoice.Id); + s.Driver.FindElement(By.Id("refundlink")).Click(); + Assert.Contains("pull-payments", s.Driver.Url); + } + + [Fact(Timeout = TestTimeout)] + [Trait("Altcoins", "Altcoins")] + [Trait("Lightning", "Lightning")] + public async Task CanUsePaymentMethodDropdown() + { + using (var s = SeleniumTester.Create()) + { + s.Server.ActivateLTC(); + s.Server.ActivateLightning(); + await s.StartAsync(); + s.GoToRegister(); + s.RegisterNewUser(); + var store = s.CreateNewStore(); + s.AddDerivationScheme("BTC"); + + //check that there is no dropdown since only one payment method is set + var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); + s.GoToInvoiceCheckout(invoiceId); + s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); + s.GoToHome(); + s.GoToStore(store.storeId); + s.AddDerivationScheme("LTC"); + s.AddLightningNode("BTC", LightningConnectionType.CLightning); + //there should be three now + invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); + s.GoToInvoiceCheckout(invoiceId); + var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + Assert.Contains("BTC", currencyDropdownButton.Text); + currencyDropdownButton.Click(); + + var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); + Assert.Equal(3, elements.Count); + elements.Single(element => element.Text.Contains("LTC")).Click(); + currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + Assert.Contains("LTC", currencyDropdownButton.Text); + currencyDropdownButton.Click(); + + elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); + elements.Single(element => element.Text.Contains("Lightning")).Click(); + + currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + Assert.Contains("Lightning", currencyDropdownButton.Text); + + s.Driver.Quit(); + } + } + + [Fact(Timeout = TestTimeout)] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + public async Task CanPayWithTwoCurrencies() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLTC(); + await tester.StartAsync(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + // First we try payment with a merchant having only BTC + var invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 5000.0m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + var cashCow = tester.ExplorerNode; + cashCow.Generate(2); // get some money in case + var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); + var firstPayment = Money.Coins(0.04m); + cashCow.SendToAddress(invoiceAddress, firstPayment); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.True(invoice.BtcPaid == firstPayment); + }); + + Assert.Single(invoice.CryptoInfo); // Only BTC should be presented + + var controller = tester.PayTester.GetController(null); + var checkout = + (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) + .GetAwaiter().GetResult()).Value; + Assert.Single(checkout.AvailableCryptos); + Assert.Equal("BTC", checkout.CryptoCode); + + Assert.Single(invoice.PaymentCodes); + Assert.Single(invoice.SupportedTransactionCurrencies); + Assert.Single(invoice.SupportedTransactionCurrencies); + Assert.Single(invoice.PaymentSubtotals); + Assert.Single(invoice.PaymentTotals); + Assert.True(invoice.PaymentCodes.ContainsKey("BTC")); + Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC")); + Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled); + Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC")); + Assert.True(invoice.PaymentTotals.ContainsKey("BTC")); + ////////////////////// + + // Retry now with LTC enabled + user.RegisterDerivationScheme("LTC"); + invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 5000.0m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + cashCow = tester.ExplorerNode; + invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); + firstPayment = Money.Coins(0.04m); + cashCow.SendToAddress(invoiceAddress, firstPayment); + Logs.Tester.LogInformation("First payment sent to " + invoiceAddress); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.True(invoice.BtcPaid == firstPayment); + }); + + cashCow = tester.LTCExplorerNode; + var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC"); + Assert.NotNull(ltcCryptoInfo); + invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network); + var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture)); + cashCow.Generate(4); // LTC is not worth a lot, so just to make sure we have money... + cashCow.SendToAddress(invoiceAddress, secondPayment); + Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal(Money.Zero, invoice.BtcDue); + var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC"); + Assert.Equal(Money.Zero, ltcPaid.Due); + Assert.Equal(secondPayment, ltcPaid.CryptoPaid); + Assert.Equal("paid", invoice.Status); + Assert.False((bool)((JValue)invoice.ExceptionStatus).Value); + }); + + controller = tester.PayTester.GetController(null); + checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC") + .GetAwaiter().GetResult()).Value; + Assert.Equal(2, checkout.AvailableCryptos.Count); + Assert.Equal("LTC", checkout.CryptoCode); + + + Assert.Equal(2, invoice.PaymentCodes.Count()); + Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count()); + Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count()); + Assert.Equal(2, invoice.PaymentSubtotals.Count()); + Assert.Equal(2, invoice.PaymentTotals.Count()); + Assert.True(invoice.PaymentCodes.ContainsKey("LTC")); + Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC")); + Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); + Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); + Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); + + + // Check if we can disable LTC + invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 5000.0m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true, + SupportedTransactionCurrencies = new Dictionary() + { + {"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}} + } + }, Facade.Merchant); + + Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC")); + Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC")); + } + } + + [Fact] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + public async Task CanUsePoSApp() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLTC(); + await tester.StartAsync(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + user.RegisterDerivationScheme("LTC"); + var apps = user.GetController(); + var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); + vm.Name = "test"; + vm.SelectedAppType = AppType.PointOfSale.ToString(); + Assert.IsType(apps.CreateApp(vm).Result); + var appId = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model) + .Apps[0].Id; + var vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + vmpos.Title = "hello"; + vmpos.Currency = "CAD"; + vmpos.ButtonText = "{0} Purchase"; + vmpos.CustomButtonText = "Nicolas Sexy Hair"; + vmpos.CustomTipText = "Wanna tip?"; + vmpos.CustomTipPercentages = "15,18,20"; + vmpos.Template = @" +apple: + price: 5.0 + title: good apple +orange: + price: 10.0 +donation: + price: 1.02 + custom: true +"; + Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + Assert.Equal("hello", vmpos.Title); + + var publicApps = user.GetController(); + var vmview = + Assert.IsType(Assert + .IsType(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model); + Assert.Equal("hello", vmview.Title); + Assert.Equal(3, 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.Equal("{0} Purchase", vmview.ButtonText); + Assert.Equal("Nicolas Sexy Hair", vmview.CustomButtonText); + Assert.Equal("Wanna tip?", vmview.CustomTipText); + Assert.Equal("15,18,20", string.Join(',', vmview.CustomTipPercentages)); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "orange").Result); + + // + var invoices = user.BitPay.GetInvoices(); + var orangeInvoice = invoices.First(); + Assert.Equal(10.00m, orangeInvoice.Price); + Assert.Equal("CAD", orangeInvoice.Currency); + Assert.Equal("orange", orangeInvoice.ItemDesc); + + + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "apple").Result); + + invoices = user.BitPay.GetInvoices(); + var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple")); + Assert.NotNull(appleInvoice); + Assert.Equal("good apple", appleInvoice.ItemDesc); + + + // testing custom amount + var action = Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 6.6m, null, null, null, null, "donation").Result); + Assert.Equal(nameof(InvoiceController.Checkout), action.ActionName); + invoices = user.BitPay.GetInvoices(); + var donationInvoice = invoices.Single(i => i.Price == 6.6m); + Assert.NotNull(donationInvoice); + Assert.Equal("CAD", donationInvoice.Currency); + Assert.Equal("donation", donationInvoice.ItemDesc); + + foreach (var test in new[] + { + (Code: "EUR", ExpectedSymbol: "€", ExpectedDecimalSeparator: ",", ExpectedDivisibility: 2, + ExpectedThousandSeparator: "\xa0", ExpectedPrefixed: false, ExpectedSymbolSpace: true), + (Code: "INR", ExpectedSymbol: "₹", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 2, + ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: true), + (Code: "JPY", ExpectedSymbol: "¥", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 0, + ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: false), + (Code: "BTC", ExpectedSymbol: "₿", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 8, + ExpectedThousandSeparator: ",", ExpectedPrefixed: false, ExpectedSymbolSpace: true), + }) + { + Logs.Tester.LogInformation($"Testing for {test.Code}"); + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + vmpos.Title = "hello"; + vmpos.Currency = test.Item1; + vmpos.ButtonText = "{0} Purchase"; + vmpos.CustomButtonText = "Nicolas Sexy Hair"; + vmpos.CustomTipText = "Wanna tip?"; + vmpos.Template = @" +apple: + price: 1000.0 + title: good apple +orange: + price: 10.0 +donation: + price: 1.02 + custom: true +"; + Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); + publicApps = user.GetController(); + vmview = Assert.IsType(Assert + .IsType(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model); + Assert.Equal(test.Code, vmview.CurrencyCode); + Assert.Equal(test.ExpectedSymbol, + vmview.CurrencySymbol.Replace("¥", "¥")); // Hack so JPY test pass on linux as well); + Assert.Equal(test.ExpectedSymbol, + vmview.CurrencyInfo.CurrencySymbol + .Replace("¥", "¥")); // Hack so JPY test pass on linux as well); + Assert.Equal(test.ExpectedDecimalSeparator, vmview.CurrencyInfo.DecimalSeparator); + Assert.Equal(test.ExpectedThousandSeparator, vmview.CurrencyInfo.ThousandSeparator); + Assert.Equal(test.ExpectedPrefixed, vmview.CurrencyInfo.Prefixed); + Assert.Equal(test.ExpectedDivisibility, vmview.CurrencyInfo.Divisibility); + Assert.Equal(test.ExpectedSymbolSpace, vmview.CurrencyInfo.SymbolSpace); + } + + + //test inventory related features + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + vmpos.Title = "hello"; + vmpos.Currency = "BTC"; + vmpos.Template = @" +inventoryitem: + price: 1.0 + title: good apple + inventory: 1 +noninventoryitem: + price: 10.0"; + Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); + + //inventoryitem has 1 item available + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result); + //we already bought all available stock so this should fail + await Task.Delay(100); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result); + + //inventoryitem has unlimited items available + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result); + + //verify invoices where created + invoices = user.BitPay.GetInvoices(); + Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem"))); + var inventoryItemInvoice = + Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem"))); + Assert.NotNull(inventoryItemInvoice); + + //let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock + var controller = tester.PayTester.GetController(user.UserId, user.StoreId); + var appService = tester.PayTester.GetService(); + var eventAggregator = tester.PayTester.GetService(); + Assert.IsType(await controller.ChangeInvoiceState(inventoryItemInvoice.Id, "invalid")); + //check that item is back in stock + TestUtils.Eventually(() => + { + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + Assert.Equal(1, + appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory); + }, 10000); + + + //test payment methods option + + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + vmpos.Title = "hello"; + vmpos.Currency = "BTC"; + vmpos.Template = @" +btconly: + price: 1.0 + title: good apple + payment_methods: + - BTC +normal: + price: 1.0"; + Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "btconly").Result); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "normal").Result); + invoices = user.BitPay.GetInvoices(); + var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal"); + var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly"); + Assert.Single(btcOnlyInvoice.CryptoInfo); + Assert.Equal("BTC", + btcOnlyInvoice.CryptoInfo.First().CryptoCode); + Assert.Equal(PaymentTypes.BTCLike.ToString(), + btcOnlyInvoice.CryptoInfo.First().PaymentType); + + Assert.Equal(2, normalInvoice.CryptoInfo.Length); + Assert.Contains( + normalInvoice.CryptoInfo, + s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains( + s.CryptoCode)); + } + } + } +} diff --git a/BTCPayServer.Tests/AltcoinTests/ElementsTests.cs b/BTCPayServer.Tests/AltcoinTests/ElementsTests.cs index 035cba800..5edff1848 100644 --- a/BTCPayServer.Tests/AltcoinTests/ElementsTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/ElementsTests.cs @@ -66,6 +66,7 @@ namespace BTCPayServer.Tests Assert.NotNull(options.NetworkProvider.GetNetwork("USDT")); } + [Fact] [Trait("Altcoins", "Altcoins")] public async Task ElementsAssetsAreHandledCorrectly() diff --git a/BTCPayServer.Tests/CheckoutUITests.cs b/BTCPayServer.Tests/CheckoutUITests.cs index 5eb52f8b7..e41104dab 100644 --- a/BTCPayServer.Tests/CheckoutUITests.cs +++ b/BTCPayServer.Tests/CheckoutUITests.cs @@ -103,53 +103,6 @@ namespace BTCPayServer.Tests } } - [Fact(Timeout = TestTimeout)] - [Trait("Altcoins", "Altcoins")] - [Trait("Lightning", "Lightning")] - public async Task CanUsePaymentMethodDropdown() - { - using (var s = SeleniumTester.Create()) - { - s.Server.ActivateLTC(); - s.Server.ActivateLightning(); - await s.StartAsync(); - s.GoToRegister(); - s.RegisterNewUser(); - var store = s.CreateNewStore(); - s.AddDerivationScheme("BTC"); - - //check that there is no dropdown since only one payment method is set - var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); - s.GoToInvoiceCheckout(invoiceId); - s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); - s.GoToHome(); - s.GoToStore(store.storeId); - s.AddDerivationScheme("LTC"); - s.AddLightningNode("BTC", LightningConnectionType.CLightning); - //there should be three now - invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); - s.GoToInvoiceCheckout(invoiceId); - var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); - Assert.Contains("BTC", currencyDropdownButton.Text); - currencyDropdownButton.Click(); - - var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); - Assert.Equal(3, elements.Count); - elements.Single(element => element.Text.Contains("LTC")).Click(); - currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); - Assert.Contains("LTC", currencyDropdownButton.Text); - currencyDropdownButton.Click(); - - elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); - elements.Single(element => element.Text.Contains("Lightning")).Click(); - - currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); - Assert.Contains("Lightning", currencyDropdownButton.Text); - - s.Driver.Quit(); - } - } - [Fact(Timeout = TestTimeout)] [Trait("Lightning", "Lightning")] public async Task CanUseLightningSatsFeature() diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 87129ca07..d214abb64 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -12,6 +12,7 @@ using BTCPayServer.Views.Wallets; using Microsoft.EntityFrameworkCore; using NBitcoin; using NBitcoin.Payment; +using NBitpayClient; using OpenQA.Selenium; using Xunit; using Xunit.Abstractions; @@ -686,80 +687,6 @@ namespace BTCPayServer.Tests } } - [Fact] - [Trait("Selenium", "Selenium")] - [Trait("Altcoins", "Altcoins")] - public async Task CanCreateRefunds() - { - using (var s = SeleniumTester.Create()) - { - s.Server.ActivateLTC(); - await s.StartAsync(); - var user = s.Server.NewAccount(); - await user.GrantAccessAsync(); - s.GoToLogin(); - s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); - user.RegisterDerivationScheme("BTC"); - await s.Server.ExplorerNode.GenerateAsync(1); - - foreach (var multiCurrency in new[] { false, true }) - { - if (multiCurrency) - user.RegisterDerivationScheme("LTC"); - foreach (var rateSelection in new[] { "FiatText", "CurrentRateText", "RateThenText" }) - await CanCreateRefundsCore(s, user, multiCurrency, rateSelection); - } - } - } - - private static async Task CanCreateRefundsCore(SeleniumTester s, TestAccount user, bool multiCurrency, string rateSelection) - { - s.GoToHome(); - s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m, 5100.0m)); - var invoice = await user.BitPay.CreateInvoiceAsync(new NBitpayClient.Invoice() - { - Currency = "USD", - Price = 5000.0m - }); - var info = invoice.CryptoInfo.First(o => o.CryptoCode == "BTC"); - var totalDue = decimal.Parse(info.TotalDue, CultureInfo.InvariantCulture); - var paid = totalDue + 0.1m; - await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(info.Address, Network.RegTest), Money.Coins(paid)); - await s.Server.ExplorerNode.GenerateAsync(1); - await TestUtils.EventuallyAsync(async () => - { - invoice = await user.BitPay.GetInvoiceAsync(invoice.Id); - Assert.Equal("confirmed", invoice.Status); - }); - - // BTC crash by 50% - s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m / 2.0m, 5100.0m / 2.0m)); - s.GoToInvoice(invoice.Id); - s.Driver.FindElement(By.Id("refundlink")).Click(); - if (multiCurrency) - { - s.Driver.FindElement(By.Id("SelectedPaymentMethod")).SendKeys("BTC" + Keys.Enter); - s.Driver.FindElement(By.Id("ok")).Click(); - } - Assert.Contains("$5,500.00", s.Driver.PageSource); // Should propose reimburse in fiat - Assert.Contains("1.10000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the rate of before - Assert.Contains("2.20000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the current rate - s.Driver.FindElement(By.Id(rateSelection)).Click(); - s.Driver.FindElement(By.Id("ok")).Click(); - Assert.Contains("pull-payments", s.Driver.Url); - if (rateSelection == "FiatText") - Assert.Contains("$5,500.00", s.Driver.PageSource); - if (rateSelection == "CurrentRateText") - Assert.Contains("2.20000000 ₿", s.Driver.PageSource); - if (rateSelection == "RateThenText") - Assert.Contains("1.10000000 ₿", s.Driver.PageSource); - s.GoToHome(); - s.GoToInvoices(); - s.GoToInvoice(invoice.Id); - s.Driver.FindElement(By.Id("refundlink")).Click(); - Assert.Contains("pull-payments", s.Driver.Url); - } - [Fact] [Trait("Selenium", "Selenium")] public async Task CanUsePullPaymentsViaUI() diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 9cb775455..698122649 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -63,7 +63,7 @@ namespace BTCPayServer.Tests PayTester.SSHConnection = GetEnvironment("TESTS_SSHCONNECTION", "root@127.0.0.1:21622"); PayTester.SocksEndpoint = GetEnvironment("TESTS_SOCKSENDPOINT", "localhost:9050"); } - +#if ALTCOINS public void ActivateLTC() { LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("LTC").NBitcoinNetwork); @@ -78,7 +78,7 @@ namespace BTCPayServer.Tests PayTester.Chains.Add("LBTC"); PayTester.LBTCNBXplorerUri = LBTCExplorerClient.Address; } - +#endif public void ActivateLightning() { var btc = NetworkProvider.GetNetwork("BTC").NBitcoinNetwork; @@ -170,20 +170,21 @@ namespace BTCPayServer.Tests { get; set; } - +#if ALTCOINS public RPCClient LTCExplorerNode { get; set; } public RPCClient LBTCExplorerNode { get; set; } + public ExplorerClient LTCExplorerClient { get; set; } + public ExplorerClient LBTCExplorerClient { get; set; } +#endif public ExplorerClient ExplorerClient { get; set; } - public ExplorerClient LTCExplorerClient { get; set; } - public ExplorerClient LBTCExplorerClient { get; set; } readonly HttpClient _Http = new HttpClient(); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 6be6bdc52..598a2a71e 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -1816,76 +1816,6 @@ namespace BTCPayServer.Tests } } - [Fact(Timeout = TestTimeout)] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - public async Task CanHaveLTCOnlyStore() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLTC(); - await tester.StartAsync(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("LTC"); - - // First we try payment with a merchant having only BTC - var invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 500, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - Assert.Single(invoice.CryptoInfo); - Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode); - Assert.True(invoice.PaymentCodes.ContainsKey("LTC")); - Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC")); - Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); - Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); - Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); - var cashCow = tester.LTCExplorerNode; - var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); - var firstPayment = Money.Coins(0.1m); - cashCow.SendToAddress(invoiceAddress, firstPayment); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid); - }); - - Assert.Single(invoice.CryptoInfo); // Only BTC should be presented - - var controller = tester.PayTester.GetController(null); - var checkout = - (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) - .GetAwaiter().GetResult()).Value; - Assert.Single(checkout.AvailableCryptos); - Assert.Equal("LTC", checkout.CryptoCode); - - ////////////////////// - - // Despite it is called BitcoinAddress it should be LTC because BTC is not available - Assert.Null(invoice.BitcoinAddress); - Assert.NotEqual(1.0m, invoice.Rate); - Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate - cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due); - - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal("paid", invoice.Status); - checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) - .GetAwaiter().GetResult()).Value; - Assert.Equal("paid", checkout.Status); - }); - } - } - [Fact(Timeout = TestTimeout)] [Trait("Integration", "Integration")] public async Task CanModifyRates() @@ -1953,145 +1883,6 @@ namespace BTCPayServer.Tests } } - [Fact(Timeout = TestTimeout)] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - public async Task CanPayWithTwoCurrencies() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLTC(); - await tester.StartAsync(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("BTC"); - // First we try payment with a merchant having only BTC - var invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 5000.0m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - var cashCow = tester.ExplorerNode; - cashCow.Generate(2); // get some money in case - var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); - var firstPayment = Money.Coins(0.04m); - cashCow.SendToAddress(invoiceAddress, firstPayment); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.True(invoice.BtcPaid == firstPayment); - }); - - Assert.Single(invoice.CryptoInfo); // Only BTC should be presented - - var controller = tester.PayTester.GetController(null); - var checkout = - (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) - .GetAwaiter().GetResult()).Value; - Assert.Single(checkout.AvailableCryptos); - Assert.Equal("BTC", checkout.CryptoCode); - - Assert.Single(invoice.PaymentCodes); - Assert.Single(invoice.SupportedTransactionCurrencies); - Assert.Single(invoice.SupportedTransactionCurrencies); - Assert.Single(invoice.PaymentSubtotals); - Assert.Single(invoice.PaymentTotals); - Assert.True(invoice.PaymentCodes.ContainsKey("BTC")); - Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC")); - Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled); - Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC")); - Assert.True(invoice.PaymentTotals.ContainsKey("BTC")); - ////////////////////// - - // Retry now with LTC enabled - user.RegisterDerivationScheme("LTC"); - invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 5000.0m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - cashCow = tester.ExplorerNode; - invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); - firstPayment = Money.Coins(0.04m); - cashCow.SendToAddress(invoiceAddress, firstPayment); - Logs.Tester.LogInformation("First payment sent to " + invoiceAddress); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.True(invoice.BtcPaid == firstPayment); - }); - - cashCow = tester.LTCExplorerNode; - var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC"); - Assert.NotNull(ltcCryptoInfo); - invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network); - var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture)); - cashCow.Generate(4); // LTC is not worth a lot, so just to make sure we have money... - cashCow.SendToAddress(invoiceAddress, secondPayment); - Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal(Money.Zero, invoice.BtcDue); - var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC"); - Assert.Equal(Money.Zero, ltcPaid.Due); - Assert.Equal(secondPayment, ltcPaid.CryptoPaid); - Assert.Equal("paid", invoice.Status); - Assert.False((bool)((JValue)invoice.ExceptionStatus).Value); - }); - - controller = tester.PayTester.GetController(null); - checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC") - .GetAwaiter().GetResult()).Value; - Assert.Equal(2, checkout.AvailableCryptos.Count); - Assert.Equal("LTC", checkout.CryptoCode); - - - Assert.Equal(2, invoice.PaymentCodes.Count()); - Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count()); - Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count()); - Assert.Equal(2, invoice.PaymentSubtotals.Count()); - Assert.Equal(2, invoice.PaymentTotals.Count()); - Assert.True(invoice.PaymentCodes.ContainsKey("LTC")); - Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC")); - Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); - Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); - Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); - - - // Check if we can disable LTC - invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 5000.0m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true, - SupportedTransactionCurrencies = new Dictionary() - { - {"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}} - } - }, Facade.Merchant); - - Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC")); - Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC")); - } - } - [Fact] [Trait("Fast", "Fast")] public void HasCurrencyDataForNetworks() @@ -2222,210 +2013,6 @@ namespace BTCPayServer.Tests parsed.ToString()); } - [Fact] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - [Trait("Lightning", "Lightning")] - public async Task CanAddDerivationSchemes() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLTC(); - tester.ActivateLightning(); - await tester.StartAsync(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("BTC"); - user.RegisterDerivationScheme("LTC"); - user.RegisterLightningNode("BTC", LightningConnectionType.CLightning); - var btcNetwork = tester.PayTester.Networks.GetNetwork("BTC"); - var invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 1.5m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - Assert.Equal(3, invoice.CryptoInfo.Length); - - var controller = user.GetController(); - var lightningVM = - (LightningNodeViewModel)Assert.IsType(controller.AddLightningNode(user.StoreId, "BTC")) - .Model; - Assert.True(lightningVM.Enabled); - lightningVM.Enabled = false; - controller.AddLightningNode(user.StoreId, lightningVM, "save", "BTC").GetAwaiter().GetResult(); - lightningVM = - (LightningNodeViewModel)Assert.IsType(controller.AddLightningNode(user.StoreId, "BTC")) - .Model; - Assert.False(lightningVM.Enabled); - - // Only Enabling/Disabling the payment method must redirect to store page - var derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - Assert.True(derivationVM.Enabled); - derivationVM.Enabled = false; - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - Assert.False(derivationVM.Enabled); - - // Clicking next without changing anything should send to the confirmation screen - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - - invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 1.5m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - Assert.Single(invoice.CryptoInfo); - Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode); - - // Removing the derivation scheme, should redirect to store page - var oldScheme = derivationVM.DerivationScheme; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.DerivationScheme = null; - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - // Setting it again should redirect to the confirmation page - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.DerivationScheme = oldScheme; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - - - //cobo vault file - var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}"; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet3.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - //wasabi wallet file - content = - "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}"; - - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet4.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - - // Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network) - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - content = - "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet.json", content); - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.False(derivationVM - .Confirmation); // Should fail, we are giving a mainnet file to a testnet network - - // And with a good file? (upub) - content = - "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet2.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - - // Now let's check that no data has been lost in the process - var store = tester.PayTester.StoreRepository.FindStore(user.StoreId).GetAwaiter().GetResult(); - var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks) - .OfType().First(o => o.PaymentId.IsBTCOnChain); - DerivationSchemeSettings.TryParseFromWalletFile(content, onchainBTC.Network, out var expected); - Assert.Equal(expected.ToJson(), onchainBTC.ToJson()); - - // Let's check that the root hdkey and account key path are taken into account when making a PSBT - invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 1.5m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - tester.ExplorerNode.Generate(1); - var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == "BTC").Address, - tester.ExplorerNode.Network); - tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(1m)); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal("paid", invoice.Status); - }); - var wallet = tester.PayTester.GetController(); - var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC, - new WalletSendModel() - { - Outputs = new List() - { - new WalletSendModel.TransactionOutput() - { - Amount = 0.5m, - DestinationAddress = new Key().PubKey.GetAddress(btcNetwork.NBitcoinNetwork) - .ToString(), - } - }, - FeeSatoshiPerByte = 1 - }, default).GetAwaiter().GetResult(); - - Assert.NotNull(psbt); - - var root = new Mnemonic( - "usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage") - .DeriveExtKey().AsHDKeyCache(); - var account = root.Derive(new KeyPath("m/49'/0'/0'")); - Assert.All(psbt.PSBT.Inputs, input => - { - var keyPath = input.HDKeyPaths.Single(); - Assert.False(keyPath.Value.KeyPath.IsHardened); - Assert.Equal(account.Derive(keyPath.Value.KeyPath).GetPublicKey(), keyPath.Key); - Assert.Equal(keyPath.Value.MasterFingerprint, - onchainBTC.AccountKeySettings[0].AccountKey.GetPublicKey().GetHDFingerPrint()); - }); - } - } - [Fact] [Trait("Integration", "Integration")] public async Task CanSetPaymentMethodLimits() @@ -2493,228 +2080,6 @@ namespace BTCPayServer.Tests } } - [Fact] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - public async Task CanUsePoSApp() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLTC(); - await tester.StartAsync(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("BTC"); - user.RegisterDerivationScheme("LTC"); - var apps = user.GetController(); - var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); - vm.Name = "test"; - vm.SelectedAppType = AppType.PointOfSale.ToString(); - Assert.IsType(apps.CreateApp(vm).Result); - var appId = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model) - .Apps[0].Id; - var vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - vmpos.Title = "hello"; - vmpos.Currency = "CAD"; - vmpos.ButtonText = "{0} Purchase"; - vmpos.CustomButtonText = "Nicolas Sexy Hair"; - vmpos.CustomTipText = "Wanna tip?"; - vmpos.CustomTipPercentages = "15,18,20"; - vmpos.Template = @" -apple: - price: 5.0 - title: good apple -orange: - price: 10.0 -donation: - price: 1.02 - custom: true -"; - Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - Assert.Equal("hello", vmpos.Title); - - var publicApps = user.GetController(); - var vmview = - Assert.IsType(Assert - .IsType(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model); - Assert.Equal("hello", vmview.Title); - Assert.Equal(3, 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.Equal("{0} Purchase", vmview.ButtonText); - Assert.Equal("Nicolas Sexy Hair", vmview.CustomButtonText); - Assert.Equal("Wanna tip?", vmview.CustomTipText); - Assert.Equal("15,18,20", string.Join(',', vmview.CustomTipPercentages)); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "orange").Result); - - // - var invoices = user.BitPay.GetInvoices(); - var orangeInvoice = invoices.First(); - Assert.Equal(10.00m, orangeInvoice.Price); - Assert.Equal("CAD", orangeInvoice.Currency); - Assert.Equal("orange", orangeInvoice.ItemDesc); - - - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "apple").Result); - - invoices = user.BitPay.GetInvoices(); - var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple")); - Assert.NotNull(appleInvoice); - Assert.Equal("good apple", appleInvoice.ItemDesc); - - - // testing custom amount - var action = Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 6.6m, null, null, null, null, "donation").Result); - Assert.Equal(nameof(InvoiceController.Checkout), action.ActionName); - invoices = user.BitPay.GetInvoices(); - var donationInvoice = invoices.Single(i => i.Price == 6.6m); - Assert.NotNull(donationInvoice); - Assert.Equal("CAD", donationInvoice.Currency); - Assert.Equal("donation", donationInvoice.ItemDesc); - - foreach (var test in new[] - { - (Code: "EUR", ExpectedSymbol: "€", ExpectedDecimalSeparator: ",", ExpectedDivisibility: 2, - ExpectedThousandSeparator: "\xa0", ExpectedPrefixed: false, ExpectedSymbolSpace: true), - (Code: "INR", ExpectedSymbol: "₹", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 2, - ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: true), - (Code: "JPY", ExpectedSymbol: "¥", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 0, - ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: false), - (Code: "BTC", ExpectedSymbol: "₿", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 8, - ExpectedThousandSeparator: ",", ExpectedPrefixed: false, ExpectedSymbolSpace: true), - }) - { - Logs.Tester.LogInformation($"Testing for {test.Code}"); - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - vmpos.Title = "hello"; - vmpos.Currency = test.Item1; - vmpos.ButtonText = "{0} Purchase"; - vmpos.CustomButtonText = "Nicolas Sexy Hair"; - vmpos.CustomTipText = "Wanna tip?"; - vmpos.Template = @" -apple: - price: 1000.0 - title: good apple -orange: - price: 10.0 -donation: - price: 1.02 - custom: true -"; - Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); - publicApps = user.GetController(); - vmview = Assert.IsType(Assert - .IsType(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model); - Assert.Equal(test.Code, vmview.CurrencyCode); - Assert.Equal(test.ExpectedSymbol, - vmview.CurrencySymbol.Replace("¥", "¥")); // Hack so JPY test pass on linux as well); - Assert.Equal(test.ExpectedSymbol, - vmview.CurrencyInfo.CurrencySymbol - .Replace("¥", "¥")); // Hack so JPY test pass on linux as well); - Assert.Equal(test.ExpectedDecimalSeparator, vmview.CurrencyInfo.DecimalSeparator); - Assert.Equal(test.ExpectedThousandSeparator, vmview.CurrencyInfo.ThousandSeparator); - Assert.Equal(test.ExpectedPrefixed, vmview.CurrencyInfo.Prefixed); - Assert.Equal(test.ExpectedDivisibility, vmview.CurrencyInfo.Divisibility); - Assert.Equal(test.ExpectedSymbolSpace, vmview.CurrencyInfo.SymbolSpace); - } - - - //test inventory related features - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - vmpos.Title = "hello"; - vmpos.Currency = "BTC"; - vmpos.Template = @" -inventoryitem: - price: 1.0 - title: good apple - inventory: 1 -noninventoryitem: - price: 10.0"; - Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); - - //inventoryitem has 1 item available - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result); - //we already bought all available stock so this should fail - await Task.Delay(100); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result); - - //inventoryitem has unlimited items available - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result); - - //verify invoices where created - invoices = user.BitPay.GetInvoices(); - Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem"))); - var inventoryItemInvoice = - Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem"))); - Assert.NotNull(inventoryItemInvoice); - - //let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock - var controller = tester.PayTester.GetController(user.UserId, user.StoreId); - var appService = tester.PayTester.GetService(); - var eventAggregator = tester.PayTester.GetService(); - Assert.IsType(await controller.ChangeInvoiceState(inventoryItemInvoice.Id, "invalid")); - //check that item is back in stock - TestUtils.Eventually(() => - { - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - Assert.Equal(1, - appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory); - }, 10000); - - - //test payment methods option - - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - vmpos.Title = "hello"; - vmpos.Currency = "BTC"; - vmpos.Template = @" -btconly: - price: 1.0 - title: good apple - payment_methods: - - BTC -normal: - price: 1.0"; - Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "btconly").Result); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "normal").Result); - invoices = user.BitPay.GetInvoices(); - var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal"); - var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly"); - Assert.Single(btcOnlyInvoice.CryptoInfo); - Assert.Equal("BTC", - btcOnlyInvoice.CryptoInfo.First().CryptoCode); - Assert.Equal(PaymentTypes.BTCLike.ToString(), - btcOnlyInvoice.CryptoInfo.First().PaymentType); - - Assert.Equal(2, normalInvoice.CryptoInfo.Length); - Assert.Contains( - normalInvoice.CryptoInfo, - s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains( - s.CryptoCode)); - } - } - - [Fact] [Trait("Fast", "Fast")] public async Task CanScheduleBackgroundTasks() @@ -3719,40 +3084,6 @@ normal: Assert.True(settings.AccountDerivation is DirectDerivationStrategy s3 && s3.Segwit); } - [Fact(Timeout = TestTimeout)] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - [Trait("Lightning", "Lightning")] - public async Task CanCreateInvoiceWithSpecificPaymentMethods() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLightning(); - tester.ActivateLTC(); - await tester.StartAsync(); - await tester.EnsureChannelsSetup(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterLightningNode("BTC", LightningConnectionType.Charge); - user.RegisterDerivationScheme("BTC"); - user.RegisterDerivationScheme("LTC"); - - var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC")); - Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count); - - - invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC") - { - SupportedTransactionCurrencies = new Dictionary() - { - {"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}} - } - }); - - Assert.Single(invoice.SupportedTransactionCurrencies); - } - } - [Fact(Timeout = TestTimeout)] [Trait("Integration", "Integration")] diff --git a/BTCPayServer/Services/BTCPayServerEnvironment.cs b/BTCPayServer/Services/BTCPayServerEnvironment.cs index fbd146a55..8cf1100d7 100644 --- a/BTCPayServer/Services/BTCPayServerEnvironment.cs +++ b/BTCPayServer/Services/BTCPayServerEnvironment.cs @@ -72,6 +72,9 @@ namespace BTCPayServer.Services { StringBuilder txt = new StringBuilder(); txt.Append($"@Copyright BTCPayServer v{Version}"); +#if ALTCOINS + txt.Append($" (altcoins)"); +#endif if (!Environment.IsProduction() || !Build.Equals("Release", StringComparison.OrdinalIgnoreCase)) { txt.Append($" Environment: {Environment.EnvironmentName} Build: {Build}");