mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-19 05:33:31 +01:00
Allow auto approval of claims for pull payments (#1851)
* Allow auto approval of claims for pull payments closes #1780 * fix
This commit is contained in:
parent
273bc78db3
commit
ed1a7bb887
@ -4,5 +4,5 @@ namespace BTCPayServer.Client.Models;
|
||||
public class CreatePayoutThroughStoreRequest : CreatePayoutRequest
|
||||
{
|
||||
public string? PullPaymentId { get; set; }
|
||||
public bool Approved { get; set; }
|
||||
public bool? Approved { get; set; }
|
||||
}
|
||||
|
@ -22,5 +22,6 @@ namespace BTCPayServer.Client.Models
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? StartsAt { get; set; }
|
||||
public string[] PaymentMethods { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -31,5 +31,6 @@ namespace BTCPayServer.Client.Models
|
||||
public TimeSpan BOLT11Expiration { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
public string ViewLink { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1412,6 +1412,28 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||
Assert.Contains(bolt, s.Driver.PageSource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//auto-approve pull payments
|
||||
|
||||
s.GoToStore(StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true);
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).Clear();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -135,7 +135,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Amount = request.Amount,
|
||||
Currency = request.Currency,
|
||||
StoreId = storeId,
|
||||
PaymentMethodIds = paymentMethods
|
||||
PaymentMethodIds = paymentMethods,
|
||||
AutoApproveClaims = request.AutoApproveClaims
|
||||
});
|
||||
var pp = await _pullPaymentService.GetPullPayment(ppId, false);
|
||||
return this.Ok(CreatePullPaymentData(pp));
|
||||
@ -155,6 +156,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Currency = ppBlob.Currency,
|
||||
Period = ppBlob.Period,
|
||||
Archived = pp.Archived,
|
||||
AutoApproveClaims = ppBlob.AutoApproveClaims,
|
||||
BOLT11Expiration = ppBlob.BOLT11Expiration,
|
||||
ViewLink = _linkGenerator.GetUriByAction(
|
||||
nameof(UIPullPaymentController.ViewPullPayment),
|
||||
|
@ -161,7 +161,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting approval.",
|
||||
Message = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting {(result.PayoutData.State == PayoutState.AwaitingApproval? "approval": "payment")}.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
}
|
||||
|
@ -139,7 +139,8 @@ namespace BTCPayServer.Controllers
|
||||
PaymentMethodIds = selectedPaymentMethodIds,
|
||||
EmbeddedCSS = model.EmbeddedCSS,
|
||||
CustomCSSLink = model.CustomCSSLink,
|
||||
BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration)
|
||||
BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration),
|
||||
AutoApproveClaims = model.AutoApproveClaims
|
||||
});
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
|
@ -30,6 +30,8 @@ namespace BTCPayServer.Data
|
||||
[JsonProperty(ItemConverterType = typeof(PaymentMethodIdJsonConverter))]
|
||||
public PaymentMethodId[] SupportedPaymentMethods { get; set; }
|
||||
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
|
||||
public class PullPaymentView
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
@ -35,6 +35,7 @@ namespace BTCPayServer.HostedServices
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public PaymentMethodId[] PaymentMethodIds { get; set; }
|
||||
public TimeSpan? Period { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
public TimeSpan? BOLT11Expiration { get; set; }
|
||||
}
|
||||
|
||||
@ -117,6 +118,7 @@ namespace BTCPayServer.HostedServices
|
||||
Limit = create.Amount,
|
||||
Period = o.Period is long periodSeconds ? (TimeSpan?)TimeSpan.FromSeconds(periodSeconds) : null,
|
||||
SupportedPaymentMethods = create.PaymentMethodIds,
|
||||
AutoApproveClaims = create.AutoApproveClaims,
|
||||
View = new PullPaymentBlob.PullPaymentView()
|
||||
{
|
||||
Title = create.Name ?? string.Empty,
|
||||
@ -422,14 +424,6 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
|
||||
if (req.ClaimRequest.PreApprove && !withoutPullPayment &&
|
||||
ppBlob.Currency != req.ClaimRequest.PaymentMethodId.CryptoCode)
|
||||
{
|
||||
req.Completion.TrySetResult(
|
||||
new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.PaymentMethodNotSupported));
|
||||
return;
|
||||
}
|
||||
|
||||
var payoutHandler =
|
||||
_payoutHandlers.FindPayoutHandler(req.ClaimRequest.PaymentMethodId);
|
||||
if (payoutHandler is null)
|
||||
@ -484,8 +478,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20)),
|
||||
Date = now,
|
||||
State =
|
||||
req.ClaimRequest.PreApprove ? PayoutState.AwaitingPayment : PayoutState.AwaitingApproval,
|
||||
State = PayoutState.AwaitingApproval,
|
||||
PullPaymentDataId = req.ClaimRequest.PullPaymentId,
|
||||
PaymentMethodId = req.ClaimRequest.PaymentMethodId.ToString(),
|
||||
Destination = req.ClaimRequest.Destination.Id,
|
||||
@ -494,7 +487,6 @@ namespace BTCPayServer.HostedServices
|
||||
var payoutBlob = new PayoutBlob()
|
||||
{
|
||||
Amount = claimed,
|
||||
CryptoAmount = req.ClaimRequest.PreApprove ? claimed : null,
|
||||
Destination = req.ClaimRequest.Destination.ToString()
|
||||
};
|
||||
payout.SetBlob(payoutBlob, _jsonSerializerSettings);
|
||||
@ -503,6 +495,24 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
await payoutHandler.TrackClaim(req.ClaimRequest.PaymentMethodId, req.ClaimRequest.Destination);
|
||||
await ctx.SaveChangesAsync();
|
||||
if (req.ClaimRequest.PreApprove.GetValueOrDefault(ppBlob?.AutoApproveClaims is true) )
|
||||
{
|
||||
payout.StoreData = await ctx.Stores.FindAsync(payout.StoreDataId);
|
||||
var rateResult = await GetRate(payout, null, CancellationToken.None);
|
||||
if (rateResult.BidAsk != null)
|
||||
{
|
||||
var approveResult = new TaskCompletionSource<PayoutApproval.Result>();
|
||||
await HandleApproval(new PayoutApproval()
|
||||
{
|
||||
PayoutId = payout.Id, Revision = payoutBlob.Revision, Rate = rateResult.BidAsk.Ask, Completion =approveResult
|
||||
});
|
||||
|
||||
if ((await approveResult.Task) == PayoutApproval.Result.Ok)
|
||||
{
|
||||
payout.State = PayoutState.AwaitingPayment;
|
||||
}
|
||||
}
|
||||
}
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.Ok, payout));
|
||||
await _notificationSender.SendNotification(new StoreScope(payout.StoreDataId),
|
||||
new PayoutNotification()
|
||||
@ -702,6 +712,6 @@ namespace BTCPayServer.HostedServices
|
||||
public decimal? Value { get; set; }
|
||||
public IClaimDestination Destination { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public bool PreApprove { get; set; }
|
||||
public bool? PreApprove { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public ProgressModel Progress { get; set; }
|
||||
public DateTimeOffset StartDate { get; set; }
|
||||
public DateTimeOffset? EndDate { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
public bool Archived { get; set; } = false;
|
||||
}
|
||||
|
||||
@ -62,5 +63,7 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
[Display(Name = "Minimum acceptable expiration time for BOLT11 for refunds")]
|
||||
[Range(1, 365 * 10)]
|
||||
public long BOLT11Expiration { get; set; } = 30;
|
||||
[Display(Name = "Automatically approve claims")]
|
||||
public bool AutoApproveClaims { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.NotificationViewModels;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
@ -37,7 +38,7 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
||||
vm.Body = (notification.Status ?? PayoutState.AwaitingApproval) switch
|
||||
{
|
||||
PayoutState.AwaitingApproval => $"A new payout is awaiting for approval",
|
||||
PayoutState.AwaitingPayment => $"A new payout is awaiting for payment",
|
||||
PayoutState.AwaitingPayment => $"A new payout is approved and awaiting payment",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(UIStorePullPaymentsController.Payouts),
|
||||
@ -50,6 +51,7 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
||||
public string StoreId { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public PayoutState? State { get; set; }
|
||||
public override string Identifier => TYPE;
|
||||
public override string NotificationType => TYPE;
|
||||
public PayoutState? Status { get; set; }
|
||||
|
@ -6,11 +6,11 @@
|
||||
}
|
||||
|
||||
@section PageHeadContent {
|
||||
<link href="~/vendor/summernote/summernote-bs5.css" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="~/vendor/summernote/summernote-bs5.css" rel="stylesheet" asp-append-version="true"/>
|
||||
}
|
||||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
<partial name="_ValidationScriptsPartial"/>
|
||||
<script src="~/vendor/summernote/summernote-bs5.js" asp-append-version="true"></script>
|
||||
}
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
<input type="submit" value="Create" class="btn btn-primary" id="Create"/>
|
||||
</div>
|
||||
|
||||
<partial name="_StatusMessage" />
|
||||
|
||||
<partial name="_StatusMessage"/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
@ -33,7 +33,7 @@
|
||||
<div class="row">
|
||||
<div class="form-group col-8">
|
||||
<label asp-for="Amount" class="form-label" data-required></label>
|
||||
<input asp-for="Amount" class="form-control" inputmode="decimal" />
|
||||
<input asp-for="Amount" class="form-control" inputmode="decimal"/>
|
||||
<span asp-validation-for="Amount" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
@ -41,6 +41,14 @@
|
||||
<input asp-for="Currency" currency-selection class="form-control"/>
|
||||
<span asp-validation-for="Currency" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-12">
|
||||
<div class="form-check ">
|
||||
<input asp-for="AutoApproveClaims" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="AutoApproveClaims" class="form-check-label"></label>
|
||||
<span asp-validation-for="AutoApproveClaims" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label asp-for="PaymentMethods" class="form-label"></label>
|
||||
|
@ -103,6 +103,7 @@
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Automatically Approved</th>
|
||||
<th scope="col">Refunded</th>
|
||||
<th scope="col" class="text-end" >Actions</th>
|
||||
</tr>
|
||||
@ -113,6 +114,7 @@
|
||||
<tr>
|
||||
<td>@pp.StartDate.ToBrowserDate()</td>
|
||||
<td>@pp.Name</td>
|
||||
<td>@pp.AutoApproveClaims</td>
|
||||
<td class="align-middle">
|
||||
<div class="progress ppProgress" data-pp="@pp.Id" data-bs-toggle="tooltip" data-bs-html="true">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.CompletedPercent"
|
||||
|
Loading…
Reference in New Issue
Block a user