diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 6c97c6629..84d6801a5 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -1979,17 +1979,15 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC"); s.Driver.FindElement(By.Id("Create")).Click(); s.Driver.FindElement(By.LinkText("View")).Click(); + + // Bitcoin-only, SelectedPaymentMethod should not be displayed + s.Driver.ElementDoesNotExist(By.Id("SelectedPaymentMethod")); var bolt = (await s.Server.CustomerLightningD.CreateInvoice( payoutAmount, $"LN payout test {DateTime.UtcNow.Ticks}", TimeSpan.FromHours(1), CancellationToken.None)).BOLT11; s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt); - s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click(); - s.Driver.FindElement(By.CssSelector( - $"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike)}]")) - .Click(); - s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter); //we do not allow short-life bolts. s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error); @@ -2000,11 +1998,6 @@ namespace BTCPayServer.Tests TimeSpan.FromDays(31), CancellationToken.None)).BOLT11; s.Driver.FindElement(By.Id("Destination")).Clear(); s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt); - s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click(); - s.Driver.FindElement(By.CssSelector( - $"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike)}]")) - .Click(); - s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter); s.FindAlertMessage(); diff --git a/BTCPayServer/Controllers/UIPullPaymentController.cs b/BTCPayServer/Controllers/UIPullPaymentController.cs index ee79c7197..ee48d6b65 100644 --- a/BTCPayServer/Controllers/UIPullPaymentController.cs +++ b/BTCPayServer/Controllers/UIPullPaymentController.cs @@ -69,14 +69,14 @@ namespace BTCPayServer.Controllers var storeBlob = store.GetStoreBlob(); var payouts = (await ctx.Payouts.GetPayoutInPeriod(pp) - .OrderByDescending(o => o.Date) - .ToListAsync()) - .Select(o => new - { - Entity = o, - Blob = o.GetBlob(_serializerSettings), - ProofBlob = _payoutHandlers.FindPayoutHandler(o.GetPaymentMethodId())?.ParseProof(o) - }); + .OrderByDescending(o => o.Date) + .ToListAsync()) + .Select(o => new + { + Entity = o, + Blob = o.GetBlob(_serializerSettings), + ProofBlob = _payoutHandlers.FindPayoutHandler(o.GetPaymentMethodId())?.ParseProof(o) + }); var cd = _currencyNameTable.GetCurrencyData(blob.Currency, false); var totalPaid = payouts.Where(p => p.Entity.State != PayoutState.Cancelled).Select(p => p.Blob.Amount).Sum(); var amountDue = blob.Limit - totalPaid; @@ -91,18 +91,17 @@ namespace BTCPayServer.Controllers CurrencyData = cd, StartDate = pp.StartDate, LastRefreshed = DateTime.UtcNow, - Payouts = payouts - .Select(entity => new ViewPullPaymentModel.PayoutLine - { - Id = entity.Entity.Id, - Amount = entity.Blob.Amount, - Currency = blob.Currency, - Status = entity.Entity.State, - Destination = entity.Blob.Destination, - PaymentMethod = PaymentMethodId.Parse(entity.Entity.PaymentMethodId), - Link = entity.ProofBlob?.Link, - TransactionId = entity.ProofBlob?.Id - }).ToList() + Payouts = payouts.Select(entity => new ViewPullPaymentModel.PayoutLine + { + Id = entity.Entity.Id, + Amount = entity.Blob.Amount, + Currency = blob.Currency, + Status = entity.Entity.State, + Destination = entity.Blob.Destination, + PaymentMethod = PaymentMethodId.Parse(entity.Entity.PaymentMethodId), + Link = entity.ProofBlob?.Link, + TransactionId = entity.ProofBlob?.Id + }).ToList() }; vm.IsPending &= vm.AmountDue > 0.0m; @@ -176,31 +175,53 @@ namespace BTCPayServer.Controllers [HttpPost("pull-payments/{pullPaymentId}/claim")] public async Task ClaimPullPayment(string pullPaymentId, ViewPullPaymentModel vm, CancellationToken cancellationToken) { - using var ctx = _dbContextFactory.CreateContext(); + await using var ctx = _dbContextFactory.CreateContext(); var pp = await ctx.PullPayments.FindAsync(pullPaymentId); if (pp is null) { ModelState.AddModelError(nameof(pullPaymentId), "This pull payment does not exists"); } - var ppBlob = pp.GetBlob(); - - var paymentMethodId = ppBlob.SupportedPaymentMethods.FirstOrDefault(id => vm.SelectedPaymentMethod == id.ToString()); - - var payoutHandler = paymentMethodId is null ? null : _payoutHandlers.FindPayoutHandler(paymentMethodId); - if (payoutHandler is null) + if (string.IsNullOrEmpty(vm.Destination)) { - ModelState.AddModelError(nameof(vm.SelectedPaymentMethod), "Invalid destination with selected payment method"); + ModelState.AddModelError(nameof(vm.Destination), "Please provide a destination"); return await ViewPullPayment(pullPaymentId); } - var destination = await payoutHandler.ParseAndValidateClaimDestination(paymentMethodId, vm.Destination, ppBlob, cancellationToken); - if (destination.destination is null) + + var ppBlob = pp.GetBlob(); + var supported = ppBlob.SupportedPaymentMethods; + PaymentMethodId paymentMethodId = null; + IClaimDestination destination = null; + if (string.IsNullOrEmpty(vm.SelectedPaymentMethod)) { - ModelState.AddModelError(nameof(vm.Destination), destination.error ?? "Invalid destination with selected payment method"); + foreach (var pmId in supported) + { + var handler = _payoutHandlers.FindPayoutHandler(pmId); + (IClaimDestination dst, string err) = handler == null + ? (null, "No payment handler found for this payment method") + : await handler.ParseAndValidateClaimDestination(pmId, vm.Destination, ppBlob, cancellationToken); + if (dst is not null && err is null) + { + paymentMethodId = pmId; + destination = dst; + break; + } + } + } + else + { + paymentMethodId = supported.FirstOrDefault(id => vm.SelectedPaymentMethod == id.ToString()); + var payoutHandler = paymentMethodId is null ? null : _payoutHandlers.FindPayoutHandler(paymentMethodId); + destination = payoutHandler is null ? null : (await payoutHandler.ParseAndValidateClaimDestination(paymentMethodId, vm.Destination, ppBlob, cancellationToken)).destination; + } + + if (destination is null) + { + ModelState.AddModelError(nameof(vm.Destination), "Invalid destination or payment method"); return await ViewPullPayment(pullPaymentId); } - var amtError = ClaimRequest.IsPayoutAmountOk(destination.destination, vm.ClaimedAmount == 0? null: vm.ClaimedAmount, paymentMethodId.CryptoCode, ppBlob.Currency); + var amtError = ClaimRequest.IsPayoutAmountOk(destination, vm.ClaimedAmount == 0? null: vm.ClaimedAmount, paymentMethodId.CryptoCode, ppBlob.Currency); if (amtError.error is not null) { ModelState.AddModelError(nameof(vm.ClaimedAmount), amtError.error ); @@ -215,9 +236,9 @@ namespace BTCPayServer.Controllers return await ViewPullPayment(pullPaymentId); } - var result = await _pullPaymentHostedService.Claim(new ClaimRequest() + var result = await _pullPaymentHostedService.Claim(new ClaimRequest { - Destination = destination.destination, + Destination = destination, PullPaymentId = pullPaymentId, Value = vm.ClaimedAmount, PaymentMethodId = paymentMethodId @@ -225,14 +246,9 @@ namespace BTCPayServer.Controllers if (result.Result != ClaimRequest.ClaimResult.Ok) { - if (result.Result == ClaimRequest.ClaimResult.AmountTooLow) - { - ModelState.AddModelError(nameof(vm.ClaimedAmount), ClaimRequest.GetErrorMessage(result.Result)); - } - else - { - ModelState.AddModelError(string.Empty, ClaimRequest.GetErrorMessage(result.Result)); - } + ModelState.AddModelError( + result.Result == ClaimRequest.ClaimResult.AmountTooLow ? nameof(vm.ClaimedAmount) : string.Empty, + ClaimRequest.GetErrorMessage(result.Result)); return await ViewPullPayment(pullPaymentId); } diff --git a/BTCPayServer/Models/ViewPullPaymentModel.cs b/BTCPayServer/Models/ViewPullPaymentModel.cs index 113c08b88..315b495f4 100644 --- a/BTCPayServer/Models/ViewPullPaymentModel.cs +++ b/BTCPayServer/Models/ViewPullPaymentModel.cs @@ -22,6 +22,7 @@ namespace BTCPayServer.Models StoreId = data.StoreId; var blob = data.GetBlob(); PaymentMethods = blob.SupportedPaymentMethods; + BitcoinOnly = blob.SupportedPaymentMethods.All(p => p.CryptoCode == "BTC"); SelectedPaymentMethod = PaymentMethods.First().ToString(); Archived = data.Archived; AutoApprove = blob.AutoApproveClaims; @@ -66,6 +67,8 @@ namespace BTCPayServer.Models } } + public bool BitcoinOnly { get; set; } + public string StoreId { get; set; } public string SelectedPaymentMethod { get; set; } diff --git a/BTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtml b/BTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtml index f1e2a7a17..4cfcbb03b 100644 --- a/BTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtml +++ b/BTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtml @@ -30,6 +30,7 @@ + @@ -46,7 +47,7 @@
@if (Model.LnurlEndpoint is not null) { - } @@ -56,13 +57,15 @@ @Model.PaymentMethods.First().ToPrettyString() } - else + else if (!Model.BitcoinOnly) { } +
-
@@ -92,22 +95,21 @@ {

@Model.Title

} -
+
Start Date -   @Model.StartDate.ToString("g")
-
+
Last Updated -   @Model.LastRefreshed.ToString("g") -
+
+ - - +
@if (!string.IsNullOrEmpty(Model.ResetIn)) { @@ -207,10 +209,13 @@
- - - + + + + + + @if (Model.LnurlEndpoint is not null) @@ -246,11 +257,6 @@ }); } -