Add ability to move a payout from InProgress to AwaitingPayment (#6564)

This commit is contained in:
Nicolas Dorier
2025-01-21 15:37:45 +09:00
committed by GitHub
parent 6180ee9b80
commit 01250f6aee
5 changed files with 80 additions and 47 deletions

View File

@@ -113,7 +113,6 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
new SelectElement(s.Driver.FindElement(By.Id("FormId"))).SelectByValue("Email");
s.Driver.TakeScreenshot().SaveAsFile("C:\\Users\\NicolasDorier\\Downloads\\chromedriver-win64\\1.png");
s.ClickPagePrimary();
s.Driver.FindElement(By.XPath("//a[starts-with(@id, 'Edit-')]")).Click();
@@ -2175,6 +2174,55 @@ namespace BTCPayServer.Tests
Assert.Contains("PP1 Edited", s.Driver.PageSource);
}
[Fact]
[Trait("Selenium", "Selenium")]
public async Task CanUseAwaitProgressForInProgressPayout()
{
using var s = CreateSeleniumTester();
await s.StartAsync();
s.RegisterNewUser(true);
s.CreateNewStore();
s.GenerateWallet(isHotWallet: true);
await s.FundStoreWallet(denomination: 50.0m);
s.GoToStore(s.StoreId, StoreNavPages.PayoutProcessors);
s.Driver.FindElement(By.Id("Configure-BTC-CHAIN")).Click();
s.Driver.SetCheckbox(By.Id("ProcessNewPayoutsInstantly"), true);
s.ClickPagePrimary();
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
s.ClickPagePrimary();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
s.Driver.FindElement(By.Id("Amount")).Clear();
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true);
s.ClickPagePrimary();
s.Driver.FindElement(By.LinkText("View")).Click();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString() + Keys.Enter);
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
s.Driver.FindElement(By.Id("InProgress-view")).Click();
// Waiting for the payment processor to process the payment
int i = 0;
while (!s.Driver.PageSource.Contains("mass-action-select-all"))
{
s.Driver.Navigate().Refresh();
i++;
Thread.Sleep(1000);
if (i > 10)
break;
}
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
s.Driver.FindElement(By.Id("InProgress-mark-awaiting-payment")).Click();
s.Driver.FindElement(By.Id("AwaitingPayment-view")).Click();
Assert.Contains("PP1", s.Driver.PageSource);
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Lightning", "Lightning")]

View File

@@ -439,7 +439,28 @@ namespace BTCPayServer.Controllers
});
break;
}
case "mark-awaiting-payment":
await using (var context = _dbContextFactory.CreateContext())
{
var payouts = (await PullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{
States = new[] { PayoutState.InProgress },
Stores = new[] { storeId },
PayoutIds = payoutIds
}, context));
foreach (var payout in payouts)
{
payout.State = PayoutState.AwaitingPayment;
payout.Proof = null;
}
await context.SaveChangesAsync();
}
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = "Payout payments have been marked as awaiting payment",
Severity = StatusMessageModel.StatusSeverity.Success
});
break;
case "cancel":
await _pullPaymentService.Cancel(
new PullPaymentHostedService.CancelRequest(payoutIds, new[] { storeId }));

View File

@@ -339,61 +339,22 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
{
var proof = ParseProof(payout) as PayoutTransactionOnChainBlob;
var payoutBlob = payout.GetBlob(this._jsonSerializerSettings);
if (proof is null || proof.Accounted is false)
{
if (proof?.Accounted is not true)
continue;
}
foreach (var txid in proof.Candidates.ToList())
{
var explorer = _explorerClientProvider.GetExplorerClient(Network.CryptoCode);
var tx = await explorer.GetTransactionAsync(txid);
if (tx is null)
{
proof.Candidates.Remove(txid);
}
else if (tx.Confirmations >= payoutBlob.MinimumConfirmation)
continue;
if (tx.Confirmations >= payoutBlob.MinimumConfirmation)
{
payout.State = PayoutState.Completed;
proof.TransactionId = tx.TransactionHash;
updatedPayouts.Add(payout);
break;
}
else
{
var rebroadcasted = await explorer.BroadcastAsync(tx.Transaction);
if (rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_ERROR ||
rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_REJECTED)
{
proof.Candidates.Remove(txid);
}
else
{
payout.State = PayoutState.InProgress;
proof.TransactionId = tx.TransactionHash;
updatedPayouts.Add(payout);
continue;
}
}
}
if (proof.TransactionId is not null && !proof.Candidates.Contains(proof.TransactionId))
{
proof.TransactionId = null;
}
if (proof.Candidates.Count == 0)
{
if (payout.State != PayoutState.AwaitingPayment)
{
updatedPayouts.Add(payout);
}
payout.State = PayoutState.AwaitingPayment;
}
else if (proof.TransactionId is null)
{
proof.TransactionId = proof.Candidates.First();
}
if (payout.State == PayoutState.Completed)
proof.Candidates = null;
SetProofBlob(payout, proof);
@@ -402,7 +363,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
await ctx.SaveChangesAsync();
foreach (PayoutData payoutData in updatedPayouts)
{
_eventAggregator.Publish(new PayoutEvent(PayoutEvent.PayoutEventType.Updated,payoutData));
_eventAggregator.Publish(new PayoutEvent(PayoutEvent.PayoutEventType.Updated, payoutData));
}
}
catch (Exception ex)

View File

@@ -39,11 +39,11 @@
<td class="actions-col" permission="@Policies.CanModifyStoreSettings">
@if (conf.Value is null)
{
<a href="@processorsView.Factory.ConfigureLink(storeId, conf.Key, Context.Request)" text-translate="true">Configure</a>
<a id="Configure-@conf.Key" href="@processorsView.Factory.ConfigureLink(storeId, conf.Key, Context.Request)" text-translate="true">Configure</a>
}
else
{
<a href="@processorsView.Factory.ConfigureLink(storeId, conf.Key, Context.Request)" text-translate="true">Modify</a>
<a id="Configure-@conf.Key" href="@processorsView.Factory.ConfigureLink(storeId, conf.Key, Context.Request)" text-translate="true">Modify</a>
@if (await processorsView.Factory.CanRemove())
{

View File

@@ -33,6 +33,9 @@
stateActions.Add(("cancel", StringLocalizer["Cancel"]));
stateActions.Add(("mark-paid", StringLocalizer["Mark as already paid"]));
break;
case PayoutState.InProgress:
stateActions.Add(("mark-awaiting-payment", StringLocalizer["Mark as awaiting payment"]));
break;
}
}