using BTCPayServer.Tests.Logging; using System.Linq; using NBitcoin; using NBitcoin.DataEncoders; using NBitcoin.Payment; using NBitpayClient; using System; using System.Threading; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; using BTCPayServer.Services.Invoices; using Newtonsoft.Json; using System.IO; using Newtonsoft.Json.Linq; using BTCPayServer.Controllers; using Microsoft.AspNetCore.Mvc; using BTCPayServer.Authentication; using System.Diagnostics; using Microsoft.EntityFrameworkCore.Extensions; using BTCPayServer.Data; using Microsoft.EntityFrameworkCore; using BTCPayServer.Services.Rates; using Microsoft.Extensions.Caching.Memory; using BTCPayServer.Eclair; using System.Collections.Generic; namespace BTCPayServer.Tests { public class UnitTest1 { public UnitTest1(ITestOutputHelper helper) { Logs.Tester = new XUnitLog(helper) { Name = "Tests" }; Logs.LogProvider = new XUnitLogProvider(helper); } [Fact] public void CanCalculateCryptoDue() { var entity = new InvoiceEntity(); #pragma warning disable CS0618 entity.TxFee = Money.Coins(0.1m); entity.Rate = 5000; entity.Payments = new System.Collections.Generic.List(); entity.ProductInformation = new ProductInformation() { Price = 5000 }; // Some check that handling legacy stuff does not break things var cryptoData = entity.GetCryptoData("BTC", null, true); cryptoData.Calculate(); Assert.NotNull(cryptoData); Assert.Null(entity.GetCryptoData("BTC", null, false)); entity.SetCryptoData(new CryptoData() { ParentEntity = entity, Rate = entity.Rate, CryptoCode = "BTC", TxFee = entity.TxFee }); Assert.NotNull(entity.GetCryptoData("BTC", null, false)); Assert.NotNull(entity.GetCryptoData("BTC", null, true)); //////////////////// var accounting = cryptoData.Calculate(); Assert.Equal(Money.Coins(1.1m), accounting.Due); Assert.Equal(Money.Coins(1.1m), accounting.TotalDue); entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()), Accounted = true }); accounting = cryptoData.Calculate(); //Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1 Assert.Equal(Money.Coins(0.7m), accounting.Due); Assert.Equal(Money.Coins(1.2m), accounting.TotalDue); entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true }); accounting = cryptoData.Calculate(); Assert.Equal(Money.Coins(0.6m), accounting.Due); Assert.Equal(Money.Coins(1.3m), accounting.TotalDue); entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()), Accounted = true }); accounting = cryptoData.Calculate(); Assert.Equal(Money.Zero, accounting.Due); Assert.Equal(Money.Coins(1.3m), accounting.TotalDue); entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true }); accounting = cryptoData.Calculate(); Assert.Equal(Money.Zero, accounting.Due); Assert.Equal(Money.Coins(1.3m), accounting.TotalDue); entity = new InvoiceEntity(); entity.ProductInformation = new ProductInformation() { Price = 5000 }; entity.SetCryptoData(new System.Collections.Generic.Dictionary(new KeyValuePair[] { new KeyValuePair("BTC", new CryptoData() { Rate = 1000, TxFee = Money.Coins(0.1m) }), new KeyValuePair("LTC", new CryptoData() { Rate = 500, TxFee = Money.Coins(0.01m) }) })); entity.Payments = new List(); cryptoData = entity.GetCryptoData("BTC", null); accounting = cryptoData.Calculate(); Assert.Equal(Money.Coins(5.1m), accounting.Due); cryptoData = entity.GetCryptoData("LTC", null); accounting = cryptoData.Calculate(); Assert.Equal(Money.Coins(10.01m), accounting.TotalDue); entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true }); cryptoData = entity.GetCryptoData("BTC", null); accounting = cryptoData.Calculate(); Assert.Equal(Money.Coins(4.2m), accounting.Due); Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid); Assert.Equal(Money.Coins(1.0m), accounting.Paid); Assert.Equal(Money.Coins(5.2m), accounting.TotalDue); Assert.Equal(2, accounting.TxCount); cryptoData = entity.GetCryptoData("LTC", null); accounting = cryptoData.Calculate(); Assert.Equal(Money.Coins(10.01m + 0.1m * 2 - 2.0m /* 8.21m */), accounting.Due); Assert.Equal(Money.Coins(0.0m), accounting.CryptoPaid); Assert.Equal(Money.Coins(2.0m), accounting.Paid); Assert.Equal(Money.Coins(10.01m + 0.1m * 2), accounting.TotalDue); entity.Payments.Add(new PaymentEntity() { CryptoCode = "LTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true }); cryptoData = entity.GetCryptoData("BTC", null); accounting = cryptoData.Calculate(); Assert.Equal(Money.Coins(4.2m - 0.5m + 0.01m / 2), accounting.Due); Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid); Assert.Equal(Money.Coins(1.5m), accounting.Paid); Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue); // The fee for LTC added Assert.Equal(2, accounting.TxCount); cryptoData = entity.GetCryptoData("LTC", null); accounting = cryptoData.Calculate(); Assert.Equal(Money.Coins(8.21m - 1.0m + 0.01m), accounting.Due); Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid); Assert.Equal(Money.Coins(3.0m), accounting.Paid); Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.01m), accounting.TotalDue); Assert.Equal(2, accounting.TxCount); var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2); entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(remaining, new Key()), Accounted = true }); cryptoData = entity.GetCryptoData("BTC", null); accounting = cryptoData.Calculate(); Assert.Equal(Money.Zero, accounting.Due); Assert.Equal(Money.Coins(1.0m) + remaining, accounting.CryptoPaid); Assert.Equal(Money.Coins(1.5m) + remaining, accounting.Paid); Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue); Assert.Equal(accounting.Paid, accounting.TotalDue); Assert.Equal(2, accounting.TxCount); cryptoData = entity.GetCryptoData("LTC", null); accounting = cryptoData.Calculate(); Assert.Equal(Money.Zero, accounting.Due); Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid); Assert.Equal(Money.Coins(3.0m) + remaining * 2, accounting.Paid); // Paying 2 BTC fee, LTC fee removed because fully paid Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */), accounting.TotalDue); Assert.Equal(1, accounting.TxCount); Assert.Equal(accounting.Paid, accounting.TotalDue); #pragma warning restore CS0618 } [Fact] public void CanPayUsingBIP70() { using (var tester = ServerTester.Create()) { tester.Start(); var user = tester.NewAccount(); user.GrantAccess(); var invoice = user.BitPay.CreateInvoice(new Invoice() { Buyer = new Buyer() { email = "test@fwf.com" }, Price = 5000.0, Currency = "USD", PosData = "posData", OrderId = "orderId", //RedirectURL = redirect + "redirect", //NotificationURL = CallbackUri + "/notification", ItemDesc = "Some description", FullNotifications = true }, Facade.Merchant); Assert.False(invoice.Refundable); var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72); var request = url.GetPaymentRequest(); var payment = request.CreatePayment(); Transaction tx = new Transaction(); tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script))); var cashCow = tester.ExplorerNode; tx = cashCow.FundRawTransaction(tx).Transaction; tx = cashCow.SignRawTransaction(tx); payment.Transactions.Add(tx); payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey)); var ack = payment.SubmitPayment(); Assert.NotNull(ack); Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("paid", localInvoice.Status); Assert.True(localInvoice.Refundable); }); } } [Fact] public void CanUseLightMoney() { var light = LightMoney.MilliSatoshis(1); Assert.Equal("0.00000000001", light.ToString()); } //[Fact] //public void CanSendLightningPayment() //{ // using (var tester = ServerTester.Create()) // { // tester.Start(); // tester.PrepareLightning(); // } //} [Fact] public void CanUseServerInitiatedPairingCode() { using (var tester = ServerTester.Create()) { tester.Start(); var acc = tester.NewAccount(); acc.Register(); acc.CreateStore(); var controller = tester.PayTester.GetController(acc.UserId); var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel() { Facade = Facade.Merchant.ToString(), Label = "bla", PublicKey = null }).GetAwaiter().GetResult(); var pairingCode = (string)token.RouteValues["pairingCode"]; acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult(); Assert.True(acc.BitPay.TestAccess(Facade.Merchant)); } } [Fact] public void CanSendIPN() { using (var callbackServer = new CustomServer()) { using (var tester = ServerTester.Create()) { tester.Start(); var acc = tester.NewAccount(); acc.GrantAccess(); var invoice = acc.BitPay.CreateInvoice(new Invoice() { Price = 5.0, Currency = "USD", PosData = "posData", OrderId = "orderId", NotificationURL = callbackServer.GetUri().AbsoluteUri, ItemDesc = "Some description", FullNotifications = true }); BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21); tester.ExplorerNode.SendToAddress(url.Address, url.Amount); Thread.Sleep(5000); callbackServer.ProcessNextRequest((ctx) => { var ipn = new StreamReader(ctx.Request.Body).ReadToEnd(); JsonConvert.DeserializeObject(ipn); //can deserialize }); var invoice2 = acc.BitPay.GetInvoice(invoice.Id); Assert.NotNull(invoice2); } } } [Fact] public void CantPairTwiceWithSamePubkey() { using (var tester = ServerTester.Create()) { tester.Start(); var acc = tester.NewAccount(); acc.Register(); var store = acc.CreateStore(); var pairingCode = acc.BitPay.RequestClientAuthorization("test", Facade.Merchant); Assert.IsType(store.Pair(pairingCode.ToString(), acc.StoreId).GetAwaiter().GetResult()); pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant); var store2 = acc.CreateStore(); store2.Pair(pairingCode.ToString(), store2.CreatedStoreId).GetAwaiter().GetResult(); Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage); } } [Fact] public void CanRBFPayment() { using (var tester = ServerTester.Create()) { tester.Start(); var user = tester.NewAccount(); user.GrantAccess(); var invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 5000.0, Currency = "USD" }, Facade.Merchant); var payment1 = Money.Coins(0.04m); var payment2 = Money.Coins(0.08m); var tx1 = new uint256(tester.ExplorerNode.SendCommand("sendtoaddress", new object[] { invoice.BitcoinAddress.ToString(), payment1.ToString(), null, //comment null, //comment_to false, //subtractfeefromamount true, //replaceable }).ResultString); var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork); Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.Equal(payment1, invoice.BtcPaid); invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork); }); var tx = tester.ExplorerNode.GetRawTransaction(new uint256(tx1)); foreach (var input in tx.Inputs) { input.ScriptSig = Script.Empty; //Strip signatures } var change = tx.Outputs.First(o => o.Value != payment1); var output = tx.Outputs.First(o => o.Value == payment1); output.Value = payment2; output.ScriptPubKey = invoiceAddress.ScriptPubKey; change.Value -= (payment2 - payment1) * 2; //Add more fees var replaced = tester.ExplorerNode.SignRawTransaction(tx); tester.ExplorerNode.SendRawTransaction(replaced); var test = tester.ExplorerClient.Sync(user.DerivationScheme, null); Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.Equal(payment2, invoice.BtcPaid); }); } } [Fact] public void CanParseFilter() { var filter = "storeid:abc status:abed blabhbalh "; var search = new SearchString(filter); Assert.Equal("storeid:abc status:abed blabhbalh", search.ToString()); Assert.Equal("blabhbalh", search.TextSearch); Assert.Equal("abc", search.Filters["storeid"]); Assert.Equal("abed", search.Filters["status"]); } [Fact] public void TestAccessBitpayAPI() { using (var tester = ServerTester.Create()) { tester.Start(); var user = tester.NewAccount(); Assert.False(user.BitPay.TestAccess(Facade.Merchant)); user.GrantAccess(); Assert.True(user.BitPay.TestAccess(Facade.Merchant)); } } [Fact] public void CanHaveLTCOnlyStore() { using (var tester = ServerTester.Create()) { tester.Start(); var user = tester.NewAccount(); user.CryptoCode = "LTC"; user.GrantAccess(); // 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); var cashCow = tester.LTCExplorerNode; var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); var firstPayment = Money.Coins(0.1m); cashCow.SendToAddress(invoiceAddress, firstPayment); 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.0, invoice.Rate); Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due); 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] public void CanPayWithTwoCurrencies() { using (var tester = ServerTester.Create()) { tester.Start(); var user = tester.NewAccount(); user.GrantAccess(); // First we try payment with a merchant having only BTC var invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 5000.0, Currency = "USD", PosData = "posData", OrderId = "orderId", ItemDesc = "Some description", FullNotifications = true }, Facade.Merchant); var cashCow = tester.ExplorerNode; var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); var firstPayment = Money.Coins(0.04m); cashCow.SendToAddress(invoiceAddress, firstPayment); 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); ////////////////////// // Retry now with LTC enabled user.RegisterDerivationScheme("LTC"); invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 5000.0, 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); 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)); cashCow.Generate(2); // LTC is not worth a lot, so just to make sure we have money... cashCow.SendToAddress(invoiceAddress, secondPayment); 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); } } [Fact] public void InvoiceFlowThroughDifferentStatesCorrectly() { using (var tester = ServerTester.Create()) { tester.Start(); var user = tester.NewAccount(); user.GrantAccess(); var invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 5000.0, Currency = "USD", PosData = "posData", OrderId = "orderId", ItemDesc = "Some description", FullNotifications = true }, Facade.Merchant); var repo = tester.PayTester.GetService(); var ctx = tester.PayTester.GetService().CreateContext(); Eventually(() => { var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery() { StoreId = user.StoreId, TextSearch = invoice.OrderId }).GetAwaiter().GetResult(); Assert.Single(textSearchResult); textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery() { StoreId = user.StoreId, TextSearch = invoice.Id }).GetAwaiter().GetResult(); Assert.Single(textSearchResult); }); invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal(Money.Coins(0), invoice.BtcPaid); Assert.Equal("new", invoice.Status); Assert.False((bool)((JValue)invoice.ExceptionStatus).Value); Assert.Single(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime)); Assert.Empty(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime + TimeSpan.FromDays(1))); Assert.Single(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5))); Assert.Single(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime)); Assert.Empty(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime - TimeSpan.FromDays(1))); var firstPayment = Money.Coins(0.04m); var txFee = Money.Zero; var rate = user.BitPay.GetRates(); var cashCow = tester.ExplorerNode; var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); var iii = ctx.AddressInvoices.ToArray(); Assert.True(IsMapped(invoice, ctx)); cashCow.SendToAddress(invoiceAddress, firstPayment); var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult(); Assert.Single(invoiceEntity.HistoricalAddresses); Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned); Money secondPayment = Money.Zero; Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("new", localInvoice.Status); Assert.Equal(firstPayment, localInvoice.BtcPaid); txFee = localInvoice.BtcDue - invoice.BtcDue; Assert.Equal("paidPartial", localInvoice.ExceptionStatus.ToString()); Assert.NotEqual(localInvoice.BitcoinAddress, invoice.BitcoinAddress); //New address Assert.True(IsMapped(invoice, ctx)); Assert.True(IsMapped(localInvoice, ctx)); invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult(); var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress.ToString()); Assert.NotNull(historical1.UnAssigned); var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress.ToString()); Assert.Null(historical2.UnAssigned); invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network); secondPayment = localInvoice.BtcDue; }); cashCow.SendToAddress(invoiceAddress, secondPayment); Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("paid", localInvoice.Status); Assert.Equal(firstPayment + secondPayment, localInvoice.BtcPaid); Assert.Equal(Money.Zero, localInvoice.BtcDue); Assert.Equal(localInvoice.BitcoinAddress, invoiceAddress.ToString()); //no new address generated Assert.True(IsMapped(localInvoice, ctx)); Assert.False((bool)((JValue)localInvoice.ExceptionStatus).Value); }); cashCow.Generate(1); //The user has medium speed settings, so 1 conf is enough to be confirmed Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("confirmed", localInvoice.Status); }); cashCow.Generate(5); //Now should be complete Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("complete", localInvoice.Status); Assert.NotEqual(0.0, localInvoice.Rate); }); invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 5000.0, Currency = "USD", PosData = "posData", OrderId = "orderId", //RedirectURL = redirect + "redirect", //NotificationURL = CallbackUri + "/notification", ItemDesc = "Some description", FullNotifications = true }, Facade.Merchant); invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1)); Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("paid", localInvoice.Status); Assert.Equal(Money.Zero, localInvoice.BtcDue); Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value); }); cashCow.Generate(1); Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("confirmed", localInvoice.Status); Assert.Equal(Money.Zero, localInvoice.BtcDue); Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value); }); } } [Fact] public void CheckRatesProvider() { var coinAverage = new CoinAverageRateProvider("BTC"); var jpy = coinAverage.GetRateAsync("JPY").GetAwaiter().GetResult(); var jpy2 = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/"))).GetRateAsync("JPY").GetAwaiter().GetResult(); var cached = new CachedRateProvider("BTC", coinAverage, new MemoryCache(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) })); cached.CacheSpan = TimeSpan.FromSeconds(10); var a = cached.GetRateAsync("JPY").GetAwaiter().GetResult(); var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult(); //Manually check that cache get hit after 10 sec var c = cached.GetRateAsync("JPY").GetAwaiter().GetResult(); } private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx) { var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash; return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.GetHash() == h) != null; } private void Eventually(Action act) { CancellationTokenSource cts = new CancellationTokenSource(10000); while (true) { try { act(); break; } catch (XunitException) when (!cts.Token.IsCancellationRequested) { cts.Token.WaitHandle.WaitOne(500); } } } } }