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);
        }
        public async Task CanUseCoinSelection()
        {
            using (var s = SeleniumTester.Create())
            {
                await s.StartAsync();

                var userId  = s.RegisterNewUser(true);
                var storeId = s.CreateNewStore().storeId;
                s.GenerateWallet("BTC", "", false, true);
                var walletId = new WalletId(storeId, "BTC");
                s.GoToWallet(walletId, WalletsNavPages.Receive);
                s.Driver.FindElement(By.Id("generateButton")).Click();
                var addressStr = s.Driver.FindElement(By.Id("vue-address")).GetProperty("value");
                var address    = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
                await s.Server.ExplorerNode.GenerateAsync(1);

                for (int i = 0; i < 6; i++)
                {
                    await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.0m));
                }
                var targetTx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.2m));

                var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);

                var spentOutpoint = new OutPoint(targetTx, tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
                await TestUtils.EventuallyAsync(async() =>
                {
                    var store = await s.Server.PayTester.StoreRepository.FindStore(storeId);
                    var x     = store.GetSupportedPaymentMethods(s.Server.NetworkProvider)
                                .OfType <DerivationSchemeSettings>()
                                .Single(settings => settings.PaymentId.CryptoCode == walletId.CryptoCode);
                    Assert.Contains(
                        await s.Server.PayTester.GetService <BTCPayWalletProvider>().GetWallet(walletId.CryptoCode)
                        .GetUnspentCoins(x.AccountDerivation),
                        coin => coin.OutPoint == spentOutpoint);
                });

                await s.Server.ExplorerNode.GenerateAsync(1);

                s.GoToWallet(walletId, WalletsNavPages.Send);
                s.Driver.FindElement(By.Id("advancedSettings")).Click();
                s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
                s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
                Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
                var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
                s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
                var inputSelectionSelect = s.Driver.FindElement(By.Name("SelectedInputs"));
                Assert.Single(inputSelectionSelect.FindElements(By.CssSelector("[selected]")));

                var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
                SetTransactionOutput(s, 0, bob, 0.3m);
                s.Driver.FindElement(By.Id("SendMenu")).Click();
                s.Driver.FindElement(By.Id("spendWithNBxplorer")).Click();
                s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
                var happyElement = s.AssertHappyMessage();
                var happyText    = happyElement.Text;
                var txid         = Regex.Match(happyText, @"\((.*)\)").Groups[1].Value;

                tx = await s.Server.ExplorerNode.GetRawTransactionAsync(new uint256(txid));

                Assert.Single(tx.Inputs);
                Assert.Equal(spentOutpoint, tx.Inputs[0].PrevOut);
            }
        }
        public async Task CanUseBIP79Client()
        {
            using (var s = SeleniumTester.Create())
            {
                await s.StartAsync();

                var invoiceRepository = s.Server.PayTester.GetService <InvoiceRepository>();
                // var payjoinRepository = s.Server.PayTester.GetService<PayJoinRepository>();
                // var broadcaster = s.Server.PayTester.GetService<DelayedTransactionBroadcaster>();
                s.RegisterNewUser(true);
                var receiver         = s.CreateNewStore();
                var receiverSeed     = s.GenerateWallet("BTC", "", true, true);
                var receiverWalletId = new WalletId(receiver.storeId, "BTC");

                //payjoin is not enabled by default.
                var invoiceId = s.CreateInvoice(receiver.storeId);
                s.GoToInvoiceCheckout(invoiceId);
                var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
                            .GetAttribute("href");
                Assert.DoesNotContain("bpu=", bip21);

                s.GoToHome();
                s.GoToStore(receiver.storeId);
                //payjoin is not enabled by default.
                Assert.False(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
                s.SetCheckbox(s, "PayJoinEnabled", true);
                s.Driver.FindElement(By.Id("Save")).Click();
                Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
                var sender         = s.CreateNewStore();
                var senderSeed     = s.GenerateWallet("BTC", "", true, true);
                var senderWalletId = new WalletId(sender.storeId, "BTC");
                await s.Server.ExplorerNode.GenerateAsync(1);

                await s.FundStoreWallet(senderWalletId);

                invoiceId = s.CreateInvoice(receiver.storeId);
                s.GoToInvoiceCheckout(invoiceId);
                bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
                        .GetAttribute("href");
                Assert.Contains("bpu=", bip21);

                s.GoToWalletSend(senderWalletId);
                s.Driver.FindElement(By.Id("bip21parse")).Click();
                s.Driver.SwitchTo().Alert().SendKeys(bip21);
                s.Driver.SwitchTo().Alert().Accept();
                Assert.False(string.IsNullOrEmpty(s.Driver.FindElement(By.Id("PayJoinEndpointUrl")).GetAttribute("value")));
                s.Driver.ScrollTo(By.Id("SendMenu"));
                s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
                s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
                await s.Server.WaitForEvent <NewOnChainTransactionEvent>(() =>
                {
                    s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
                    return(Task.CompletedTask);
                });

                //no funds in receiver wallet to do payjoin
                s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Warning);
                await TestUtils.EventuallyAsync(async() =>
                {
                    var invoice = await s.Server.PayTester.GetService <InvoiceRepository>().GetInvoice(invoiceId);
                    Assert.Equal(InvoiceStatus.Paid, invoice.Status);
                });

                s.GoToInvoices();
                var paymentValueRowColumn = s.Driver.FindElement(By.Id($"invoice_{invoiceId}")).FindElement(By.ClassName("payment-value"));
                Assert.False(paymentValueRowColumn.Text.Contains("payjoin", StringComparison.InvariantCultureIgnoreCase));

                //let's do it all again, except now the receiver has funds and is able to payjoin
                invoiceId = s.CreateInvoice(receiver.storeId);
                s.GoToInvoiceCheckout(invoiceId);
                bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
                        .GetAttribute("href");
                Assert.Contains("bpu", bip21);

                s.GoToWalletSend(senderWalletId);
                s.Driver.FindElement(By.Id("bip21parse")).Click();
                s.Driver.SwitchTo().Alert().SendKeys(bip21);
                s.Driver.SwitchTo().Alert().Accept();
                Assert.False(string.IsNullOrEmpty(s.Driver.FindElement(By.Id("PayJoinEndpointUrl")).GetAttribute("value")));
                s.Driver.ScrollTo(By.Id("SendMenu"));
                s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
                s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
                await s.Server.WaitForEvent <NewOnChainTransactionEvent>(() =>
                {
                    s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
                    return(Task.CompletedTask);
                });

                s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Success);
                await TestUtils.EventuallyAsync(async() =>
                {
                    var invoice  = await invoiceRepository.GetInvoice(invoiceId);
                    var payments = invoice.GetPayments();
                    Assert.Equal(2, payments.Count);
                    var originalPayment = payments[0];
                    var coinjoinPayment = payments[1];
                    Assert.Equal(-1, ((BitcoinLikePaymentData)originalPayment.GetCryptoPaymentData()).ConfirmationCount);
                    Assert.Equal(0, ((BitcoinLikePaymentData)coinjoinPayment.GetCryptoPaymentData()).ConfirmationCount);
                    Assert.False(originalPayment.Accounted);
                    Assert.True(coinjoinPayment.Accounted);
                    Assert.Equal(((BitcoinLikePaymentData)originalPayment.GetCryptoPaymentData()).Value,
                                 ((BitcoinLikePaymentData)coinjoinPayment.GetCryptoPaymentData()).Value);
                    Assert.Equal(originalPayment.GetCryptoPaymentData()
                                 .AssertType <BitcoinLikePaymentData>()
                                 .Value,
                                 coinjoinPayment.GetCryptoPaymentData()
                                 .AssertType <BitcoinLikePaymentData>()
                                 .Value);
                });

                await TestUtils.EventuallyAsync(async() =>
                {
                    var invoice = await s.Server.PayTester.GetService <InvoiceRepository>().GetInvoice(invoiceId);
                    var dto     = invoice.EntityToDTO();
                    Assert.Equal(InvoiceStatus.Paid, invoice.Status);
                });

                s.GoToInvoices();
                paymentValueRowColumn = s.Driver.FindElement(By.Id($"invoice_{invoiceId}")).FindElement(By.ClassName("payment-value"));
                Assert.False(paymentValueRowColumn.Text.Contains("payjoin", StringComparison.InvariantCultureIgnoreCase));
            }
        }
        public async Task CanUseBIP79()
        {
            using (var tester = ServerTester.Create())
            {
                await tester.StartAsync();

                ////var payJoinStateProvider = tester.PayTester.GetService<PayJoinStateProvider>();
                var btcPayNetwork = tester.NetworkProvider.GetNetwork <BTCPayNetwork>("BTC");
                var btcPayWallet  = tester.PayTester.GetService <BTCPayWalletProvider>().GetWallet(btcPayNetwork);
                var cashCow       = tester.ExplorerNode;
                cashCow.Generate(2); // get some money in case

                var senderUser = tester.NewAccount();
                senderUser.GrantAccess(true);
                senderUser.RegisterDerivationScheme("BTC", true, true);

                var invoice = senderUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 100, Currency = "USD", FullNotifications = true
                });
                //payjoin is not enabled by default.
                Assert.DoesNotContain("bpu", invoice.CryptoInfo.First().PaymentUrls.BIP21);
                cashCow.SendToAddress(BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network),
                                      Money.Coins(0.06m));

                var receiverUser = tester.NewAccount();
                receiverUser.GrantAccess(true);
                receiverUser.RegisterDerivationScheme("BTC", true, true);

                await receiverUser.EnablePayJoin();

                // payjoin is enabled, with a segwit wallet, and the keys are available in nbxplorer
                invoice = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.02m, Currency = "BTC", FullNotifications = true
                });
                cashCow.SendToAddress(BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network),
                                      Money.Coins(0.06m));
                var receiverWalletId = new WalletId(receiverUser.StoreId, "BTC");

                //give the cow some cash
                await cashCow.GenerateAsync(1);

                //let's get some more utxos first
                await receiverUser.ReceiveUTXO(Money.Coins(0.011m), btcPayNetwork);

                await receiverUser.ReceiveUTXO(Money.Coins(0.012m), btcPayNetwork);

                await receiverUser.ReceiveUTXO(Money.Coins(0.013m), btcPayNetwork);

                await receiverUser.ReceiveUTXO(Money.Coins(0.014m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.021m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.022m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.023m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.024m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.025m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.026m), btcPayNetwork);

                var senderChange = await senderUser.GetNewAddress(btcPayNetwork);

                //Let's start the harassment
                invoice = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.02m, Currency = "BTC", FullNotifications = true
                });

                var parsedBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
                                                        tester.ExplorerClient.Network.NBitcoinNetwork);

                var invoice2 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.02m, Currency = "BTC", FullNotifications = true
                });
                var secondInvoiceParsedBip21 = new BitcoinUrlBuilder(invoice2.CryptoInfo.First().PaymentUrls.BIP21,
                                                                     tester.ExplorerClient.Network.NBitcoinNetwork);

                var senderStore = await tester.PayTester.StoreRepository.FindStore(senderUser.StoreId);

                var paymentMethodId          = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
                var derivationSchemeSettings = senderStore.GetSupportedPaymentMethods(tester.NetworkProvider)
                                               .OfType <DerivationSchemeSettings>().SingleOrDefault(settings =>
                                                                                                    settings.PaymentId == paymentMethodId);

                ReceivedCoin[] senderCoins = null;
                await TestUtils.EventuallyAsync(async() =>
                {
                    senderCoins = await btcPayWallet.GetUnspentCoins(senderUser.DerivationScheme);
                    Assert.Contains(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);
                });

                var coin  = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.021m);
                var coin2 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.022m);
                var coin3 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.023m);
                var coin4 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.024m);
                var coin5 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.025m);
                var coin6 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);

                var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
                signingKeySettings.RootFingerprint =
                    senderUser.GenerateWalletResponseV.MasterHDKey.GetPublicKey().GetHDFingerPrint();

                var extKey =
                    senderUser.GenerateWalletResponseV.MasterHDKey.Derive(signingKeySettings.GetRootedKeyPath()
                                                                          .KeyPath);


                var n             = tester.ExplorerClient.Network.NBitcoinNetwork;
                var Invoice1Coin1 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                    .SetChange(senderChange)
                                    .Send(parsedBip21.Address, parsedBip21.Amount)
                                    .AddCoins(coin.Coin)
                                    .AddKeys(extKey.Derive(coin.KeyPath))
                                    .SendEstimatedFees(new FeeRate(100m))
                                    .BuildTransaction(true);

                var Invoice1Coin2 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                    .SetChange(senderChange)
                                    .Send(parsedBip21.Address, parsedBip21.Amount)
                                    .AddCoins(coin2.Coin)
                                    .AddKeys(extKey.Derive(coin2.KeyPath))
                                    .SendEstimatedFees(new FeeRate(100m))
                                    .BuildTransaction(true);

                var Invoice2Coin1 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                    .SetChange(senderChange)
                                    .Send(secondInvoiceParsedBip21.Address, secondInvoiceParsedBip21.Amount)
                                    .AddCoins(coin.Coin)
                                    .AddKeys(extKey.Derive(coin.KeyPath))
                                    .SendEstimatedFees(new FeeRate(100m))
                                    .BuildTransaction(true);

                var Invoice2Coin2 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                    .SetChange(senderChange)
                                    .Send(secondInvoiceParsedBip21.Address, secondInvoiceParsedBip21.Amount)
                                    .AddCoins(coin2.Coin)
                                    .AddKeys(extKey.Derive(coin2.KeyPath))
                                    .SendEstimatedFees(new FeeRate(100m))
                                    .BuildTransaction(true);

                //Attempt 1: Send a signed tx to invoice 1 that does not pay the invoice at all
                //Result: reject
                // Assert.False((await tester.PayTester.HttpClient.PostAsync(endpoint,
                //     new StringContent(Invoice2Coin1.ToHex(), Encoding.UTF8, "text/plain"))).IsSuccessStatusCode);

                //Attempt 2: Create two transactions using different inputs and send them to the same invoice.
                //Result: Second Tx should be rejected.
                var Invoice1Coin1ResponseTx = await senderUser.SubmitPayjoin(invoice, Invoice1Coin1, btcPayNetwork);

                await senderUser.SubmitPayjoin(invoice, Invoice1Coin1, btcPayNetwork, "already-paid");

                var contributedInputsInvoice1Coin1ResponseTx =
                    Invoice1Coin1ResponseTx.Inputs.Where(txin => coin.OutPoint != txin.PrevOut);
                Assert.Single(contributedInputsInvoice1Coin1ResponseTx);

                //Attempt 3: Send the same inputs from invoice 1 to invoice 2 while invoice 1 tx has not been broadcasted
                //Result: Reject Tx1 but accept tx 2 as its inputs were never accepted by invoice 1
                await senderUser.SubmitPayjoin(invoice2, Invoice2Coin1, btcPayNetwork, "inputs-already-used");

                var Invoice2Coin2ResponseTx = await senderUser.SubmitPayjoin(invoice2, Invoice2Coin2, btcPayNetwork);

                var contributedInputsInvoice2Coin2ResponseTx =
                    Invoice2Coin2ResponseTx.Inputs.Where(txin => coin2.OutPoint != txin.PrevOut);
                Assert.Single(contributedInputsInvoice2Coin2ResponseTx);

                //Attempt 4: Make tx that pays invoice 3 and 4 and submit to both
                //Result: reject on 4: the protocol should not worry about this complexity

                var invoice3 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice3ParsedBip21 = new BitcoinUrlBuilder(invoice3.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);


                var invoice4 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice4ParsedBip21 = new BitcoinUrlBuilder(invoice4.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);


                var Invoice3AndInvoice4Coin3 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                               .SetChange(senderChange)
                                               .Send(invoice3ParsedBip21.Address, invoice3ParsedBip21.Amount)
                                               .Send(invoice4ParsedBip21.Address, invoice4ParsedBip21.Amount)
                                               .AddCoins(coin3.Coin)
                                               .AddKeys(extKey.Derive(coin3.KeyPath))
                                               .SendEstimatedFees(new FeeRate(100m))
                                               .BuildTransaction(true);

                await senderUser.SubmitPayjoin(invoice3, Invoice3AndInvoice4Coin3, btcPayNetwork);

                await senderUser.SubmitPayjoin(invoice4, Invoice3AndInvoice4Coin3, btcPayNetwork, "already-paid");

                //Attempt 5: Make tx that pays invoice 5 with 2 outputs
                //Result: proposed tx consolidates the outputs

                var invoice5 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice5ParsedBip21 = new BitcoinUrlBuilder(invoice5.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);

                var Invoice5Coin4TxBuilder = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                             .SetChange(senderChange)
                                             .Send(invoice5ParsedBip21.Address, invoice5ParsedBip21.Amount / 2)
                                             .Send(invoice5ParsedBip21.Address, invoice5ParsedBip21.Amount / 2)
                                             .AddCoins(coin4.Coin)
                                             .AddKeys(extKey.Derive(coin4.KeyPath))
                                             .SendEstimatedFees(new FeeRate(100m));

                var Invoice5Coin4           = Invoice5Coin4TxBuilder.BuildTransaction(true);
                var Invoice5Coin4ResponseTx = await senderUser.SubmitPayjoin(invoice5, Invoice5Coin4, btcPayNetwork);

                Assert.Single(Invoice5Coin4ResponseTx.Outputs.To(invoice5ParsedBip21.Address));

                //Attempt 10: send tx with rbf, broadcast payjoin tx, bump the rbf payjoin , attempt to submit tx again
                //Result: same tx gets sent back

                //give the receiver some more utxos
                Assert.NotNull(await tester.ExplorerNode.SendToAddressAsync(
                                   (await btcPayWallet.ReserveAddressAsync(receiverUser.DerivationScheme)).Address,
                                   new Money(0.1m, MoneyUnit.BTC)));

                var invoice6 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice6ParsedBip21 = new BitcoinUrlBuilder(invoice6.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);

                var invoice6Coin5TxBuilder = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                             .SetChange(senderChange)
                                             .Send(invoice6ParsedBip21.Address, invoice6ParsedBip21.Amount)
                                             .AddCoins(coin5.Coin)
                                             .AddKeys(extKey.Derive(coin5.KeyPath))
                                             .SendEstimatedFees(new FeeRate(100m))
                                             .SetLockTime(0);

                var invoice6Coin5 = invoice6Coin5TxBuilder
                                    .BuildTransaction(true);

                var Invoice6Coin5Response1Tx = await senderUser.SubmitPayjoin(invoice6, invoice6Coin5, btcPayNetwork);

                var Invoice6Coin5Response1TxSigned = invoice6Coin5TxBuilder.SignTransaction(Invoice6Coin5Response1Tx);
                //broadcast the first payjoin
                await tester.ExplorerClient.BroadcastAsync(Invoice6Coin5Response1TxSigned);

                // invoice6Coin5TxBuilder = invoice6Coin5TxBuilder.SendEstimatedFees(new FeeRate(100m));
                // var invoice6Coin5Bumpedfee = invoice6Coin5TxBuilder
                //     .BuildTransaction(true);
                //
                // var Invoice6Coin5Response3 = await tester.PayTester.HttpClient.PostAsync(invoice6Endpoint,
                //     new StringContent(invoice6Coin5Bumpedfee.ToHex(), Encoding.UTF8, "text/plain"));
                // Assert.True(Invoice6Coin5Response3.IsSuccessStatusCode);
                // var Invoice6Coin5Response3Tx =
                //     Transaction.Parse(await Invoice6Coin5Response3.Content.ReadAsStringAsync(), n);
                // Assert.True(invoice6Coin5Bumpedfee.Inputs.All(txin =>
                //     Invoice6Coin5Response3Tx.Inputs.Any(txin2 => txin2.PrevOut == txin.PrevOut)));

                //Attempt 11:
                //send tx with rbt, broadcast payjoin,
                //create tx spending the original tx inputs with rbf to self,
                //Result: the exposed utxos are priorized in the next p2ep

                //give the receiver some more utxos
                Assert.NotNull(await tester.ExplorerNode.SendToAddressAsync(
                                   (await btcPayWallet.ReserveAddressAsync(receiverUser.DerivationScheme)).Address,
                                   new Money(0.1m, MoneyUnit.BTC)));

                var invoice7 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice7ParsedBip21 = new BitcoinUrlBuilder(invoice7.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);

                var invoice7Coin6TxBuilder = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                             .SetChange(senderChange)
                                             .Send(invoice7ParsedBip21.Address, invoice7ParsedBip21.Amount)
                                             .AddCoins(coin6.Coin)
                                             .AddKeys(extKey.Derive(coin6.KeyPath))
                                             .SendEstimatedFees(new FeeRate(100m))
                                             .SetLockTime(0);

                var invoice7Coin6Tx = invoice7Coin6TxBuilder
                                      .BuildTransaction(true);

                var invoice7Coin6Response1Tx = await senderUser.SubmitPayjoin(invoice7, invoice7Coin6Tx, btcPayNetwork);

                var Invoice7Coin6Response1TxSigned = invoice7Coin6TxBuilder.SignTransaction(invoice7Coin6Response1Tx);
                var contributedInputsInvoice7Coin6Response1TxSigned =
                    Invoice7Coin6Response1TxSigned.Inputs.Single(txin => coin6.OutPoint != txin.PrevOut);


                ////var receiverWalletPayJoinState = payJoinStateProvider.Get(receiverWalletId);
                ////Assert.Contains(receiverWalletPayJoinState.GetRecords(), item => item.InvoiceId == invoice7.Id);
                //broadcast the payjoin
                var res = (await tester.ExplorerClient.BroadcastAsync(Invoice7Coin6Response1TxSigned));
                Assert.True(res.Success);

                // Paid with coinjoin
                await TestUtils.EventuallyAsync(async() =>
                {
                    var invoiceEntity = await tester.PayTester.GetService <InvoiceRepository>().GetInvoice(invoice7.Id);
                    Assert.Equal(InvoiceStatus.Paid, invoiceEntity.Status);
                    Assert.Contains(invoiceEntity.GetPayments(), p => p.Accounted &&
                                    ((BitcoinLikePaymentData)p.GetCryptoPaymentData()).PayjoinInformation is null);
                });

                ////Assert.Contains(receiverWalletPayJoinState.GetRecords(), item => item.InvoiceId == invoice7.Id && item.TxSeen);

                var invoice7Coin6Tx2 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                       .SetChange(senderChange)
                                       .AddCoins(coin6.Coin)
                                       .SendAll(senderChange)
                                       .SubtractFees()
                                       .AddKeys(extKey.Derive(coin6.KeyPath))
                                       .SendEstimatedFees(new FeeRate(200m))
                                       .SetLockTime(0)
                                       .BuildTransaction(true);

                //broadcast the "rbf cancel" tx
                res = (await tester.ExplorerClient.BroadcastAsync(invoice7Coin6Tx2));
                Assert.True(res.Success);

                // Make a block, this should put back the invoice to new
                var blockhash = tester.ExplorerNode.Generate(1)[0];
                Assert.NotNull(await tester.ExplorerNode.GetRawTransactionAsync(invoice7Coin6Tx2.GetHash(), blockhash));
                Assert.Null(await tester.ExplorerNode.GetRawTransactionAsync(Invoice7Coin6Response1TxSigned.GetHash(), blockhash, false));
                // Now we should return to New
                OutPoint ourOutpoint = null;
                await TestUtils.EventuallyAsync(async() =>
                {
                    var invoiceEntity = await tester.PayTester.GetService <InvoiceRepository>().GetInvoice(invoice7.Id);
                    Assert.Equal(InvoiceStatus.New, invoiceEntity.Status);
                    Assert.True(invoiceEntity.GetPayments().All(p => !p.Accounted));
                    ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData().First().PayjoinInformation.ContributedOutPoints[0];
                });

                var payjoinRepository = tester.PayTester.GetService <PayJoinRepository>();
                // The outpoint should now be available for next pj selection
                Assert.False(await payjoinRepository.TryUnlock(ourOutpoint));
            }
        }
        public async Task CanUseBIP79FeeCornerCase()
        {
            using (var tester = ServerTester.Create())
            {
                await tester.StartAsync();

                var broadcaster       = tester.PayTester.GetService <DelayedTransactionBroadcaster>();
                var payjoinRepository = tester.PayTester.GetService <PayJoinRepository>();
                broadcaster.Disable();
                var network      = tester.NetworkProvider.GetNetwork <BTCPayNetwork>("BTC");
                var btcPayWallet = tester.PayTester.GetService <BTCPayWalletProvider>().GetWallet(network);
                var cashCow      = tester.ExplorerNode;
                cashCow.Generate(2); // get some money in case

                var senderUser = tester.NewAccount();
                senderUser.GrantAccess(true);
                senderUser.RegisterDerivationScheme("BTC", true);

                var receiverUser = tester.NewAccount();
                receiverUser.GrantAccess(true);
                receiverUser.RegisterDerivationScheme("BTC", true, true);
                await receiverUser.EnablePayJoin();

                var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);

                string lastInvoiceId = null;

                var vector = (SpentCoin : Money.Satoshis(810), InvoiceAmount : Money.Satoshis(700), Paid : Money.Satoshis(700), Fee : Money.Satoshis(110), ExpectLocked : false, ExpectedError : "not-enough-money");
                async Task <PSBT> RunVector()
                {
                    var coin = await senderUser.ReceiveUTXO(vector.SpentCoin, network);

                    var invoice = receiverUser.BitPay.CreateInvoice(new Invoice()
                    {
                        Price = vector.InvoiceAmount.ToDecimal(MoneyUnit.BTC), Currency = "BTC", FullNotifications = true
                    });

                    lastInvoiceId = invoice.Id;
                    var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
                    var txBuilder      = network.NBitcoinNetwork.CreateTransactionBuilder();

                    txBuilder.AddCoins(coin);
                    txBuilder.Send(invoiceAddress, vector.Paid);
                    txBuilder.SendFees(vector.Fee);
                    txBuilder.SetChange(await senderUser.GetNewAddress(network));
                    var psbt = txBuilder.BuildPSBT(false);

                    psbt = await senderUser.Sign(psbt);

                    var pj = await senderUser.SubmitPayjoin(invoice, psbt, vector.ExpectedError);

                    if (vector.ExpectLocked)
                    {
                        Assert.True(await payjoinRepository.TryUnlock(receiverCoin.Outpoint));
                    }
                    else
                    {
                        Assert.False(await payjoinRepository.TryUnlock(receiverCoin.Outpoint));
                    }
                    return(pj);
                }

                async Task LockNewReceiverCoin()
                {
                    var coins = await btcPayWallet.GetUnspentCoins(receiverUser.DerivationScheme);

                    foreach (var coin in coins.Where(c => c.OutPoint != receiverCoin.Outpoint))
                    {
                        await payjoinRepository.TryLock(coin.OutPoint);
                    }
                }

                Logs.Tester.LogInformation("Here we send exactly the right amount. This should fails as\n" +
                                           "there is not enough to pay the additional payjoin input. (going below the min relay fee" +
                                           "However, the original tx has been broadcasted!");
                vector = (SpentCoin : Money.Satoshis(810), InvoiceAmount : Money.Satoshis(700), Paid : Money.Satoshis(700), Fee : Money.Satoshis(110), ExpectLocked : false, ExpectedError : "not-enough-money");
                await RunVector();
                await LockNewReceiverCoin();

                Logs.Tester.LogInformation("We don't pay enough");
                vector = (SpentCoin : Money.Satoshis(810), InvoiceAmount : Money.Satoshis(700), Paid : Money.Satoshis(690), Fee : Money.Satoshis(110), ExpectLocked : false, ExpectedError : "invoice-not-fully-paid");
                await RunVector();

                Logs.Tester.LogInformation("We pay correctly");
                vector = (SpentCoin : Money.Satoshis(810), InvoiceAmount : Money.Satoshis(500), Paid : Money.Satoshis(500), Fee : Money.Satoshis(110), ExpectLocked : true, ExpectedError : null as string);
                await RunVector();

                Logs.Tester.LogInformation("We pay correctly, but no utxo\n" +
                                           "However, this has the side effect of having the receiver broadcasting the original tx");
                await payjoinRepository.TryLock(receiverCoin.Outpoint);

                vector = (SpentCoin : Money.Satoshis(810), InvoiceAmount : Money.Satoshis(500), Paid : Money.Satoshis(500), Fee : Money.Satoshis(110), ExpectLocked : true, ExpectedError : "out-of-utxos");
                await RunVector();
                await LockNewReceiverCoin();

                var originalSenderUser = senderUser;
retry:
                // Additional fee is 96 , minrelaytx is 294
                // We pay correctly, fees partially taken from what is overpaid
                // We paid 510, the receiver pay 10 sat
                // The send pay remaining 86 sat from his pocket
                // So total paid by sender should be 86 + 510 + 200 so we should get 1090 - (86 + 510 + 200) == 294 back)
                Logs.Tester.LogInformation($"Check if we can take fee on overpaid utxo{(senderUser == receiverUser ? " (to self)" : "")}");
                vector = (SpentCoin : Money.Satoshis(1090), InvoiceAmount : Money.Satoshis(500), Paid : Money.Satoshis(510), Fee : Money.Satoshis(200), ExpectLocked : true, ExpectedError : null as string);
                var proposedPSBT = await RunVector();

                Assert.Equal(2, proposedPSBT.Outputs.Count);
                Assert.Contains(proposedPSBT.Outputs, o => o.Value == Money.Satoshis(500) + receiverCoin.Amount);
                Assert.Contains(proposedPSBT.Outputs, o => o.Value == Money.Satoshis(294));
                proposedPSBT = await senderUser.Sign(proposedPSBT);

                proposedPSBT = proposedPSBT.Finalize();
                var explorerClient = tester.PayTester.GetService <ExplorerClientProvider>().GetExplorerClient(proposedPSBT.Network.NetworkSet.CryptoCode);
                var result         = await explorerClient.BroadcastAsync(proposedPSBT.ExtractTransaction());

                Assert.True(result.Success);
                Logs.Tester.LogInformation($"We broadcasted the payjoin {proposedPSBT.ExtractTransaction().GetHash()}");
                Logs.Tester.LogInformation($"Let's make sure that the coinjoin is not over paying, since the 10 overpaid sats have gone to fee");
                await TestUtils.EventuallyAsync(async() =>
                {
                    var invoice = await tester.PayTester.GetService <InvoiceRepository>().GetInvoice(lastInvoiceId);
                    Assert.Equal(InvoiceStatus.Paid, invoice.Status);
                    Assert.Equal(InvoiceExceptionStatus.None, invoice.ExceptionStatus);
                    var coins = await btcPayWallet.GetUnspentCoins(receiverUser.DerivationScheme);
                    foreach (var coin in coins)
                    {
                        await payjoinRepository.TryLock(coin.OutPoint);
                    }
                });

                tester.ExplorerNode.Generate(1);
                receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);

                if (senderUser != receiverUser)
                {
                    Logs.Tester.LogInformation("Let's do the same, this time paying to ourselves");
                    senderUser = receiverUser;
                    goto retry;
                }
                else
                {
                    senderUser = originalSenderUser;
                }


                // Same as above. Except the sender send one satoshi less, so the change
                // output get below dust and should be removed completely.
                vector       = (SpentCoin : Money.Satoshis(1089), InvoiceAmount : Money.Satoshis(500), Paid : Money.Satoshis(510), Fee : Money.Satoshis(200), ExpectLocked : true, ExpectedError : null as string);
                proposedPSBT = await RunVector();

                var output = Assert.Single(proposedPSBT.Outputs);
                // With the output removed, the user should have largely pay all the needed fee
                Assert.Equal(Money.Satoshis(510) + receiverCoin.Amount, output.Value);
                proposedPSBT = await senderUser.Sign(proposedPSBT);

                proposedPSBT   = proposedPSBT.Finalize();
                explorerClient = tester.PayTester.GetService <ExplorerClientProvider>().GetExplorerClient(proposedPSBT.Network.NetworkSet.CryptoCode);
                result         = await explorerClient.BroadcastAsync(proposedPSBT.ExtractTransaction(), true);

                Assert.True(result.Success);
            }
        }
Exemple #6
0
        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 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.GoToStore();
            s.Driver.FindElement(By.Id("BOLT11Expiration")).Clear();
            s.Driver.FindElement(By.Id("BOLT11Expiration")).SendKeys("5" + Keys.Enter);
            s.GoToInvoice(invoice.Id);
            s.Driver.FindElement(By.Id("IssueRefund")).Click();

            if (multiCurrency)
            {
                s.Driver.WaitUntilAvailable(By.Id("RefundForm"), TimeSpan.FromSeconds(1));
                s.Driver.WaitUntilAvailable(By.Id("SelectedPaymentMethod"), TimeSpan.FromSeconds(1));
                s.Driver.FindElement(By.Id("SelectedPaymentMethod")).SendKeys("BTC" + Keys.Enter);
                s.Driver.FindElement(By.Id("ok")).Click();
            }

            s.Driver.WaitUntilAvailable(By.Id("RefundForm"), TimeSpan.FromSeconds(1));
            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.WaitForAndClick(By.Id(rateSelection));
            s.Driver.FindElement(By.Id("ok")).Click();

            s.Driver.WaitUntilAvailable(By.Id("Destination"), TimeSpan.FromSeconds(1));
            Assert.Contains("pull-payments", s.Driver.Url);
            if (rateSelection == "FiatOption")
            {
                Assert.Contains("$5,500.00", s.Driver.PageSource);
            }
            if (rateSelection == "CurrentOption")
            {
                Assert.Contains("2.20000000 ₿", s.Driver.PageSource);
            }
            if (rateSelection == "RateThenOption")
            {
                Assert.Contains("1.10000000 ₿", s.Driver.PageSource);
            }

            s.GoToInvoice(invoice.Id);
            s.Driver.FindElement(By.Id("IssueRefund")).Click();
            s.Driver.WaitUntilAvailable(By.Id("Destination"), TimeSpan.FromSeconds(1));
            Assert.Contains("pull-payments", s.Driver.Url);
            var client = await user.CreateClient();

            var ppid = s.Driver.Url.Split('/').Last();
            var pps  = await client.GetPullPayments(user.StoreId);

            var pp = Assert.Single(pps, p => p.Id == ppid);

            Assert.Equal(TimeSpan.FromDays(5.0), pp.BOLT11Expiration);
        }
        public async Task PaymentControllerTests()
        {
            using (var tester = ServerTester.Create())
            {
                await tester.StartAsync();

                var user = tester.NewAccount();
                user.GrantAccess();
                await user.MakeAdmin();

                var client = await user.CreateClient(Policies.Unrestricted);

                var viewOnly = await user.CreateClient(Policies.CanViewPaymentRequests);

                //create payment request

                //validation errors
                await AssertHttpError(400, async() =>
                {
                    await client.CreatePaymentRequest(user.StoreId, new CreatePaymentRequestRequest()
                    {
                        Title = "A"
                    });
                });
                await AssertHttpError(400, async() =>
                {
                    await client.CreatePaymentRequest(user.StoreId,
                                                      new CreatePaymentRequestRequest()
                    {
                        Title = "A", Currency = "BTC", Amount = 0
                    });
                });
                await AssertHttpError(400, async() =>
                {
                    await client.CreatePaymentRequest(user.StoreId,
                                                      new CreatePaymentRequestRequest()
                    {
                        Title = "A", Currency = "helloinvalid", Amount = 1
                    });
                });
                await AssertHttpError(403, async() =>
                {
                    await viewOnly.CreatePaymentRequest(user.StoreId,
                                                        new CreatePaymentRequestRequest()
                    {
                        Title = "A", Currency = "helloinvalid", Amount = 1
                    });
                });

                var newPaymentRequest = await client.CreatePaymentRequest(user.StoreId,
                                                                          new CreatePaymentRequestRequest()
                {
                    Title = "A", Currency = "USD", Amount = 1
                });

                //list payment request
                var paymentRequests = await viewOnly.GetPaymentRequests(user.StoreId);

                Assert.NotNull(paymentRequests);
                Assert.Single(paymentRequests);
                Assert.Equal(newPaymentRequest.Id, paymentRequests.First().Id);

                //get payment request
                var paymentRequest = await viewOnly.GetPaymentRequest(user.StoreId, newPaymentRequest.Id);

                Assert.Equal(newPaymentRequest.Title, paymentRequest.Title);

                //update payment request
                var updateRequest = JObject.FromObject(paymentRequest).ToObject <UpdatePaymentRequestRequest>();
                updateRequest.Title = "B";
                await AssertHttpError(403, async() =>
                {
                    await viewOnly.UpdatePaymentRequest(user.StoreId, paymentRequest.Id, updateRequest);
                });

                await client.UpdatePaymentRequest(user.StoreId, paymentRequest.Id, updateRequest);

                paymentRequest = await client.GetPaymentRequest(user.StoreId, newPaymentRequest.Id);

                Assert.Equal(updateRequest.Title, paymentRequest.Title);

                //archive payment request
                await AssertHttpError(403, async() =>
                {
                    await viewOnly.ArchivePaymentRequest(user.StoreId, paymentRequest.Id);
                });

                await client.ArchivePaymentRequest(user.StoreId, paymentRequest.Id);

                Assert.DoesNotContain(paymentRequest.Id,
                                      (await client.GetPaymentRequests(user.StoreId)).Select(data => data.Id));

                //let's test some payment stuff
                await user.RegisterDerivationSchemeAsync("BTC");

                var paymentTestPaymentRequest = await client.CreatePaymentRequest(user.StoreId,
                                                                                  new CreatePaymentRequestRequest()
                {
                    Amount = 0.1m, Currency = "BTC", Title = "Payment test title"
                });

                var invoiceId = Assert.IsType <string>(Assert.IsType <OkObjectResult>(await user.GetController <PaymentRequestController>()
                                                                                      .PayPaymentRequest(paymentTestPaymentRequest.Id, false)).Value);
                var invoice = user.BitPay.GetInvoice(invoiceId);
                await tester.WaitForEvent <InvoiceDataChangedEvent>(async() =>
                {
                    await tester.ExplorerNode.SendToAddressAsync(
                        BitcoinAddress.Create(invoice.BitcoinAddress, tester.ExplorerNode.Network), invoice.BtcDue);
                });

                await TestUtils.EventuallyAsync(async() =>
                {
                    Assert.Equal(Invoice.STATUS_PAID, user.BitPay.GetInvoice(invoiceId).Status);
                    Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
                });
            }
        }