mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 05:54:26 +01:00
Decouple PaymentMethodId from PayoutMethodId (#5944)
This commit is contained in:
@@ -363,7 +363,10 @@ namespace BTCPayServer.Tests
|
||||
if (multiCurrency)
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
foreach (var rateSelection in new[] { "FiatOption", "CurrentRateOption", "RateThenOption", "CustomOption" })
|
||||
{
|
||||
TestLogs.LogInformation((multiCurrency, rateSelection).ToString());
|
||||
await CanCreateRefundsCore(s, user, multiCurrency, rateSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,11 +402,10 @@ namespace BTCPayServer.Tests
|
||||
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.WaitUntilAvailable(By.Id("SelectedPayoutMethod"), TimeSpan.FromSeconds(1));
|
||||
s.Driver.FindElement(By.Id("SelectedPayoutMethod")).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 USD", s.Driver.PageSource); // Should propose reimburse in fiat
|
||||
Assert.Contains("1.10000000 BTC", s.Driver.PageSource); // Should propose reimburse in BTC at the rate of before
|
||||
|
||||
@@ -3450,7 +3450,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(connStr, methods.FirstOrDefault(m => m.PaymentMethodId == "BTC-LN")?.Config["connectionString"].Value<string>());
|
||||
methods = await adminClient.GetStorePaymentMethods(store.Id);
|
||||
Assert.Null(methods.FirstOrDefault(m => m.PaymentMethodId == "BTC-LN").Config);
|
||||
await this.AssertValidationError(["paymentMethodId"], () => adminClient.RemoveStorePaymentMethod(store.Id, "LOL"));
|
||||
await this.AssertAPIError("paymentmethod-not-found", () => adminClient.RemoveStorePaymentMethod(store.Id, "LOL"));
|
||||
await adminClient.RemoveStorePaymentMethod(store.Id, "BTC-LN");
|
||||
|
||||
// Alternative way of setting the connection string
|
||||
@@ -3948,7 +3948,7 @@ namespace BTCPayServer.Tests
|
||||
txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m) + fee);
|
||||
}, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
||||
await tester.PayTester.GetService<PayoutProcessorService>().Restart(new PayoutProcessorService.PayoutProcessorQuery(admin.StoreId, PaymentMethodId.Parse("BTC")));
|
||||
await tester.PayTester.GetService<PayoutProcessorService>().Restart(new PayoutProcessorService.PayoutProcessorQuery(admin.StoreId, Payouts.PayoutMethodId.Parse("BTC")));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
|
||||
@@ -2094,6 +2094,7 @@ namespace BTCPayServer.Tests
|
||||
public async Task CanUsePullPaymentsViaUI()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
s.Server.DeleteStore = false;
|
||||
s.Server.ActivateLightning(LightningConnectionType.LndREST);
|
||||
await s.StartAsync();
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
@@ -2274,7 +2275,7 @@ namespace BTCPayServer.Tests
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
|
||||
var paymentMethodOptions = s.Driver.FindElements(By.CssSelector("input[name='PaymentMethods']"));
|
||||
var paymentMethodOptions = s.Driver.FindElements(By.CssSelector("input[name='PayoutMethods']"));
|
||||
Assert.Equal(2, paymentMethodOptions.Count);
|
||||
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("Lightning Test");
|
||||
@@ -2287,7 +2288,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
// Bitcoin-only, SelectedPaymentMethod should not be displayed
|
||||
s.Driver.ElementDoesNotExist(By.Id("SelectedPaymentMethod"));
|
||||
s.Driver.ElementDoesNotExist(By.Id("SelectedPayoutMethod"));
|
||||
|
||||
var bolt = (await s.Server.CustomerLightningD.CreateInvoice(
|
||||
payoutAmount,
|
||||
@@ -3072,7 +3073,7 @@ namespace BTCPayServer.Tests
|
||||
// Check that pull payment has lightning option
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), PaymentMethodId.Parse(Assert.Single(s.Driver.FindElements(By.CssSelector("input[name='PaymentMethods']"))).GetAttribute("value")));
|
||||
Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), PaymentMethodId.Parse(Assert.Single(s.Driver.FindElements(By.CssSelector("input[name='PayoutMethods']"))).GetAttribute("value")));
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
||||
|
||||
@@ -12,6 +12,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
@@ -46,6 +47,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentLinkExtension> _paymentLinkExtensions;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public LanguageService LanguageService { get; }
|
||||
@@ -58,6 +60,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
IAuthorizationService authorizationService,
|
||||
Dictionary<PaymentMethodId, IPaymentLinkExtension> paymentLinkExtensions,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
_invoiceController = invoiceController;
|
||||
@@ -71,6 +74,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_authorizationService = authorizationService;
|
||||
_paymentLinkExtensions = paymentLinkExtensions;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
_handlers = handlers;
|
||||
LanguageService = languageService;
|
||||
}
|
||||
@@ -206,10 +210,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
for (int i = 0; i < request.Checkout.PaymentMethods.Length; i++)
|
||||
{
|
||||
if (!PaymentMethodId.TryParse(request.Checkout.PaymentMethods[i], out _))
|
||||
if (
|
||||
request.Checkout.PaymentMethods[i] is not { } pm ||
|
||||
!PaymentMethodId.TryParse(pm, out var pm1) ||
|
||||
_handlers.TryGet(pm1) is null)
|
||||
{
|
||||
request.AddModelError(invoiceRequest => invoiceRequest.Checkout.PaymentMethods[i],
|
||||
"Invalid payment method", this);
|
||||
"Invalid PaymentMethodId", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,10 +401,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return this.CreateAPIError("non-refundable", "Cannot refund this invoice");
|
||||
}
|
||||
PaymentPrompt? paymentPrompt = null;
|
||||
PaymentMethodId? paymentMethodId = null;
|
||||
if (request.PaymentMethod is not null && PaymentMethodId.TryParse(request.PaymentMethod, out paymentMethodId))
|
||||
PayoutMethodId? payoutMethodId = null;
|
||||
if (request.PaymentMethod is not null && PayoutMethodId.TryParse(request.PaymentMethod, out payoutMethodId))
|
||||
{
|
||||
paymentPrompt = invoice.GetPaymentPrompt(paymentMethodId);
|
||||
var supported = _payoutHandlers.GetSupportedPayoutMethods(store);
|
||||
if (supported.Contains(payoutMethodId))
|
||||
{
|
||||
var paymentMethodId = PaymentMethodId.GetSimilarities([payoutMethodId], invoice.GetPayments(false).Select(p => p.PaymentMethodId))
|
||||
.OrderByDescending(o => o.similarity)
|
||||
.Select(o => o.b)
|
||||
.FirstOrDefault();
|
||||
paymentPrompt = paymentMethodId is null ? null : invoice.GetPaymentPrompt(paymentMethodId);
|
||||
}
|
||||
}
|
||||
if (paymentPrompt is null)
|
||||
{
|
||||
@@ -405,7 +420,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
if (request.RefundVariant is null)
|
||||
ModelState.AddModelError(nameof(request.RefundVariant), "`refundVariant` is mandatory");
|
||||
if (!ModelState.IsValid || paymentPrompt is null || paymentMethodId is null)
|
||||
if (!ModelState.IsValid || paymentPrompt is null || payoutMethodId is null)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
var accounting = paymentPrompt.Calculate();
|
||||
@@ -425,7 +440,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Name = request.Name ?? $"Refund {invoice.Id}",
|
||||
Description = request.Description,
|
||||
StoreId = storeId,
|
||||
PaymentMethodIds = new[] { paymentMethodId },
|
||||
PayoutMethodIds = new[] { payoutMethodId },
|
||||
};
|
||||
|
||||
if (request.RefundVariant != RefundVariant.Custom)
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
Name = factory.Processor,
|
||||
FriendlyName = factory.FriendlyName,
|
||||
PaymentMethods = factory.GetSupportedPaymentMethods().Select(id => id.ToString())
|
||||
PaymentMethods = factory.GetSupportedPayoutMethods().Select(id => id.ToString())
|
||||
.ToArray()
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.NTag424;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
@@ -42,7 +43,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
@@ -53,7 +54,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
Services.BTCPayNetworkJsonSerializerSettings serializerSettings,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IAuthorizationService authorizationService,
|
||||
SettingsRepository settingsRepository,
|
||||
@@ -134,18 +135,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.BOLT11Expiration), $"The BOLT11 expiration should be positive");
|
||||
}
|
||||
PaymentMethodId?[]? paymentMethods = null;
|
||||
if (request.PaymentMethods is { } paymentMethodsStr)
|
||||
PayoutMethodId?[]? payoutMethods = null;
|
||||
if (request.PaymentMethods is { } payoutMethodsStr)
|
||||
{
|
||||
paymentMethods = paymentMethodsStr.Select(s =>
|
||||
payoutMethods = payoutMethodsStr.Select(s =>
|
||||
{
|
||||
PaymentMethodId.TryParse(s, out var pmi);
|
||||
PayoutMethodId.TryParse(s, out var pmi);
|
||||
return pmi;
|
||||
}).ToArray();
|
||||
var supported = (await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData())).ToArray();
|
||||
for (int i = 0; i < paymentMethods.Length; i++)
|
||||
var supported = _payoutHandlers.GetSupportedPayoutMethods(HttpContext.GetStoreData());
|
||||
for (int i = 0; i < payoutMethods.Length; i++)
|
||||
{
|
||||
if (!supported.Contains(paymentMethods[i]))
|
||||
if (!supported.Contains(payoutMethods[i]))
|
||||
{
|
||||
request.AddModelError(paymentRequest => paymentRequest.PaymentMethods[i], "Invalid or unsupported payment method", this);
|
||||
}
|
||||
@@ -168,7 +169,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Amount = request.Amount,
|
||||
Currency = request.Currency,
|
||||
StoreId = storeId,
|
||||
PaymentMethodIds = paymentMethods,
|
||||
PayoutMethodIds = payoutMethods,
|
||||
AutoApproveClaims = request.AutoApproveClaims
|
||||
});
|
||||
var pp = await _pullPaymentService.GetPullPayment(ppId, false);
|
||||
@@ -390,7 +391,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
};
|
||||
model.Destination = blob.Destination;
|
||||
model.PaymentMethod = p.PaymentMethodId;
|
||||
model.CryptoCode = p.GetPaymentMethodId().CryptoCode;
|
||||
var currency = this._payoutHandlers.TryGet(p.GetPayoutMethodId())?.Currency;
|
||||
model.CryptoCode = currency;
|
||||
model.PaymentProof = p.GetProofBlobJson();
|
||||
return model;
|
||||
}
|
||||
@@ -399,13 +401,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> CreatePayout(string pullPaymentId, CreatePayoutRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
|
||||
if (!PayoutMethodId.TryParse(request?.PaymentMethod, out var payoutMethodId))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var payoutHandler = _payoutHandlers.FindPayoutHandler(paymentMethodId);
|
||||
var payoutHandler = _payoutHandlers.TryGet(payoutMethodId);
|
||||
if (payoutHandler is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
@@ -417,14 +419,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
if (pp is null)
|
||||
return PullPaymentNotFound();
|
||||
var ppBlob = pp.GetBlob();
|
||||
var destination = await payoutHandler.ParseAndValidateClaimDestination(paymentMethodId, request!.Destination, ppBlob, cancellationToken);
|
||||
var destination = await payoutHandler.ParseAndValidateClaimDestination(request!.Destination, ppBlob, cancellationToken);
|
||||
if (destination.destination is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Destination), destination.error ?? "The destination is invalid for the payment specified");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var amtError = ClaimRequest.IsPayoutAmountOk(destination.destination, request.Amount, paymentMethodId.CryptoCode, ppBlob.Currency);
|
||||
var amtError = ClaimRequest.IsPayoutAmountOk(destination.destination, request.Amount, payoutHandler.Currency, ppBlob.Currency);
|
||||
if (amtError.error is not null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Amount), amtError.error );
|
||||
@@ -436,7 +438,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Destination = destination.destination,
|
||||
PullPaymentId = pullPaymentId,
|
||||
Value = request.Amount,
|
||||
PaymentMethodId = paymentMethodId,
|
||||
PayoutMethodId = payoutMethodId,
|
||||
StoreId = pp.StoreId
|
||||
});
|
||||
|
||||
@@ -456,13 +458,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
}
|
||||
|
||||
if (request is null || !PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
|
||||
if (request?.PaymentMethod is null || !PayoutMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var payoutHandler = _payoutHandlers.FindPayoutHandler(paymentMethodId);
|
||||
var payoutHandler = _payoutHandlers.TryGet(paymentMethodId);
|
||||
if (payoutHandler is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
|
||||
@@ -482,7 +484,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return PullPaymentNotFound();
|
||||
ppBlob = pp.GetBlob();
|
||||
}
|
||||
var destination = await payoutHandler.ParseAndValidateClaimDestination(paymentMethodId, request!.Destination, ppBlob, default);
|
||||
var destination = await payoutHandler.ParseAndValidateClaimDestination(request!.Destination, ppBlob, default);
|
||||
if (destination.destination is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Destination), destination.error ?? "The destination is invalid for the payment specified");
|
||||
@@ -508,7 +510,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
PullPaymentId = request.PullPaymentId,
|
||||
PreApprove = request.Approved,
|
||||
Value = request.Amount,
|
||||
PaymentMethodId = paymentMethodId,
|
||||
PayoutMethodId = paymentMethodId,
|
||||
StoreId = storeId,
|
||||
Metadata = request.Metadata
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.PayoutProcessors.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
@@ -34,18 +35,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory")]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{paymentMethod}")]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{payoutMethodId}")]
|
||||
public async Task<IActionResult> GetStoreLightningAutomatedPayoutProcessors(
|
||||
string storeId, string? paymentMethod)
|
||||
string storeId, string? payoutMethodId)
|
||||
{
|
||||
var paymentMethodId = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod) : null;
|
||||
var paymentMethodId = !string.IsNullOrEmpty(payoutMethodId) ? PayoutMethodId.Parse(payoutMethodId) : null;
|
||||
var configured =
|
||||
await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { LightningAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = paymentMethodId is null ? null : new[] { paymentMethodId }
|
||||
PayoutMethodIds = paymentMethodId is null ? null : new[] { paymentMethodId }
|
||||
});
|
||||
|
||||
return Ok(configured.Select(ToModel).ToArray());
|
||||
@@ -73,27 +74,27 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{paymentMethod}")]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payout-processors/LightningAutomatedPayoutSenderFactory/{payoutMethodId}")]
|
||||
public async Task<IActionResult> UpdateStoreLightningAutomatedPayoutProcessor(
|
||||
string storeId, string paymentMethod, LightningAutomatedPayoutSettings request)
|
||||
string storeId, string payoutMethodId, LightningAutomatedPayoutSettings request)
|
||||
{
|
||||
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
var paymentMethodId = PaymentMethodId.Parse(paymentMethod);
|
||||
var pmi = PayoutMethodId.Parse(payoutMethodId);
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { LightningAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = new[] { paymentMethodId }
|
||||
PayoutMethodIds = new[] { pmi }
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.HasTypedBlob<LightningAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = paymentMethodId.ToString();
|
||||
activeProcessor.PaymentMethod = pmi.ToString();
|
||||
activeProcessor.Processor = LightningAutomatedPayoutSenderFactory.ProcessorName;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
|
||||
@@ -10,6 +10,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
@@ -39,14 +40,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
public async Task<IActionResult> GetStoreOnChainAutomatedPayoutProcessors(
|
||||
string storeId, string? paymentMethod)
|
||||
{
|
||||
var paymentMethodId = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod) : null;
|
||||
var paymentMethodId = !string.IsNullOrEmpty(paymentMethod) ? PayoutMethodId.Parse(paymentMethod) : null;
|
||||
var configured =
|
||||
await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = paymentMethodId is null ? null : new[] { paymentMethodId }
|
||||
PayoutMethodIds = paymentMethodId is null ? null : new[] { paymentMethodId }
|
||||
});
|
||||
|
||||
return Ok(configured.Select(ToModel).ToArray());
|
||||
@@ -86,20 +87,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(request.FeeBlockTarget), "The feeBlockTarget should be between 1 and 1000");
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
var paymentMethodId = PaymentMethodId.Parse(paymentMethod);
|
||||
var payoutMethodId = PayoutMethodId.Parse(paymentMethod);
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = new[] { paymentMethodId }
|
||||
PayoutMethodIds = new[] { payoutMethodId }
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.HasTypedBlob<OnChainAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = paymentMethodId.ToString();
|
||||
activeProcessor.PaymentMethod = payoutMethodId.ToString();
|
||||
activeProcessor.Processor = OnChainAutomatedPayoutSenderFactory.ProcessorName;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
|
||||
@@ -7,6 +7,7 @@ using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -54,7 +55,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { processor },
|
||||
PaymentMethods = new[] { PaymentMethodId.Parse(paymentMethod) }
|
||||
PayoutMethodIds = new[] { PayoutMethodId.Parse(paymentMethod) }
|
||||
})).FirstOrDefault();
|
||||
if (matched is null)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@ using BTCPayServer.Models.PaymentRequestViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
@@ -265,7 +266,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet("invoices/{invoiceId}/refund")]
|
||||
[Authorize(Policy = Policies.CanCreateNonApprovedPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Refund([FromServices] IEnumerable<IPayoutHandler> payoutHandlers, string invoiceId, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Refund(string invoiceId, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
@@ -290,24 +291,8 @@ namespace BTCPayServer.Controllers
|
||||
new { pullPaymentId = ppId });
|
||||
}
|
||||
|
||||
var paymentMethods = invoice.GetBlob().GetPaymentPrompts();
|
||||
var pmis = paymentMethods.Select(method => method.PaymentMethodId).ToHashSet();
|
||||
// If LNURL is contained, add the LN too as a possible option
|
||||
foreach (var pmi in pmis.ToList())
|
||||
{
|
||||
if (!_handlers.TryGetValue(pmi, out var h))
|
||||
{
|
||||
pmis.Remove(pmi);
|
||||
continue;
|
||||
}
|
||||
if (h is LNURLPayPaymentHandler lh)
|
||||
{
|
||||
pmis.Add(PaymentTypes.LN.GetPaymentMethodId(lh.Network.CryptoCode));
|
||||
}
|
||||
}
|
||||
var relevant = payoutHandlers.Where(handler => pmis.Any(handler.CanHandle));
|
||||
var options = (await relevant.GetSupportedPaymentMethods(invoice.StoreData)).Where(id => pmis.Contains(id)).ToHashSet();
|
||||
if (!options.Any())
|
||||
var payoutMethodIds = _payoutHandlers.GetSupportedPayoutMethods(this.GetCurrentStore());
|
||||
if (!payoutMethodIds.Any())
|
||||
{
|
||||
var vm = new RefundModel { Title = "No matching payment method" };
|
||||
ModelState.AddModelError(nameof(vm.AvailablePaymentMethods),
|
||||
@@ -315,18 +300,22 @@ namespace BTCPayServer.Controllers
|
||||
return View("_RefundModal", vm);
|
||||
}
|
||||
|
||||
var defaultRefund = invoice.Payments
|
||||
.Select(p => p.GetBlob())
|
||||
.Select(p => p.PaymentMethodId)
|
||||
.FirstOrDefault(p => p != null && options.Contains(p));
|
||||
// Find the most similar payment method to the one used for the invoice
|
||||
var defaultRefund =
|
||||
PaymentMethodId.GetSimilarities(
|
||||
invoice.Payments.Select(o => o.GetPaymentMethodId()),
|
||||
payoutMethodIds)
|
||||
.OrderByDescending(o => o.similarity)
|
||||
.Select(o => o.b)
|
||||
.FirstOrDefault();
|
||||
|
||||
var refund = new RefundModel
|
||||
{
|
||||
Title = "Payment method",
|
||||
AvailablePaymentMethods =
|
||||
new SelectList(options.Select(id => new SelectListItem(id.ToString(), id.ToString())),
|
||||
new SelectList(payoutMethodIds.Select(id => new SelectListItem(id.ToString(), id.ToString())),
|
||||
"Value", "Text"),
|
||||
SelectedPaymentMethod = defaultRefund?.ToString() ?? options.First().ToString()
|
||||
SelectedPayoutMethod = defaultRefund?.ToString() ?? payoutMethodIds.First().ToString()
|
||||
};
|
||||
|
||||
// Nothing to select, skip to next
|
||||
@@ -351,31 +340,35 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
|
||||
var store = GetCurrentStore();
|
||||
var paymentMethodId = PaymentMethodId.Parse(model.SelectedPaymentMethod);
|
||||
var pmi = PayoutMethodId.Parse(model.SelectedPayoutMethod);
|
||||
var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.Currency, true);
|
||||
|
||||
RateRules rules;
|
||||
RateResult rateResult;
|
||||
CreatePullPayment createPullPayment;
|
||||
var pms = invoice.GetPaymentPrompts();
|
||||
if (!pms.TryGetValue(paymentMethodId, out var paymentMethod))
|
||||
|
||||
var pmis = _payoutHandlers.GetSupportedPayoutMethods(store);
|
||||
if (!pmis.Contains(pmi))
|
||||
{
|
||||
// We included Lightning if only LNURL was set, so this must be LNURL
|
||||
if (_handlers.TryGetValue(paymentMethodId, out var h) && h is LightningLikePaymentHandler lnh)
|
||||
{
|
||||
pms.TryGetValue(PaymentTypes.LNURL.GetPaymentMethodId(lnh.Network.CryptoCode), out paymentMethod);
|
||||
}
|
||||
ModelState.AddModelError(nameof(model.SelectedPayoutMethod), $"Invalid payout method");
|
||||
return View("_RefundModal", model);
|
||||
}
|
||||
if (paymentMethod is null || paymentMethod.Currency is null)
|
||||
|
||||
var paymentMethodId = PaymentMethodId.GetSimilarities([pmi], invoice.GetPayments(false).Select(p => p.PaymentMethodId))
|
||||
.OrderByDescending(o => o.similarity)
|
||||
.Select(o => o.b)
|
||||
.FirstOrDefault();
|
||||
|
||||
var paymentMethod = paymentMethodId is null ? null : invoice.GetPaymentPrompt(paymentMethodId);
|
||||
if (paymentMethod?.Currency is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.SelectedPaymentMethod), $"Invalid payment method");
|
||||
ModelState.AddModelError(nameof(model.SelectedPayoutMethod), $"Invalid payout method");
|
||||
return View("_RefundModal", model);
|
||||
}
|
||||
|
||||
var accounting = paymentMethod.Calculate();
|
||||
decimal cryptoPaid = accounting.Paid;
|
||||
decimal dueAmount = accounting.TotalDue;
|
||||
var paymentMethodCurrency = paymentMethodId.CryptoCode;
|
||||
var paymentMethodCurrency = paymentMethod.Currency;
|
||||
|
||||
var isPaidOver = invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver;
|
||||
decimal? overpaidAmount = isPaidOver ? Math.Round(cryptoPaid - dueAmount, paymentMethod.Divisibility) : null;
|
||||
@@ -421,7 +414,7 @@ namespace BTCPayServer.Controllers
|
||||
createPullPayment = new CreatePullPayment
|
||||
{
|
||||
Name = $"Refund {invoice.Id}",
|
||||
PaymentMethodIds = new[] { paymentMethodId },
|
||||
PayoutMethodIds = new[] { pmi },
|
||||
StoreId = invoice.StoreId,
|
||||
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ using Newtonsoft.Json.Linq;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
using Serilog.Filters;
|
||||
using PeterO.Numbers;
|
||||
using BTCPayServer.Payouts;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@@ -49,6 +50,7 @@ namespace BTCPayServer.Controllers
|
||||
private readonly DisplayFormatter _displayFormatter;
|
||||
readonly EventAggregator _EventAggregator;
|
||||
readonly BTCPayNetworkProvider _NetworkProvider;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly PullPaymentHostedService _paymentHostedService;
|
||||
@@ -77,6 +79,7 @@ namespace BTCPayServer.Controllers
|
||||
EventAggregator eventAggregator,
|
||||
ContentSecurityPolicies csp,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
PullPaymentHostedService paymentHostedService,
|
||||
@@ -102,6 +105,7 @@ namespace BTCPayServer.Controllers
|
||||
_UserManager = userManager;
|
||||
_EventAggregator = eventAggregator;
|
||||
_NetworkProvider = networkProvider;
|
||||
this._payoutHandlers = payoutHandlers;
|
||||
_handlers = paymentMethodHandlerDictionary;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_paymentHostedService = paymentHostedService;
|
||||
|
||||
@@ -19,6 +19,7 @@ using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Plugins.Crowdfund;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
@@ -48,12 +49,12 @@ namespace BTCPayServer
|
||||
{
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _appService;
|
||||
private readonly UIInvoiceController _invoiceController;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly LightningAddressService _lightningAddressService;
|
||||
private readonly LightningLikePayoutHandler _lightningLikePayoutHandler;
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly IPluginHookService _pluginHookService;
|
||||
@@ -62,13 +63,13 @@ namespace BTCPayServer
|
||||
|
||||
public UILNURLController(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
StoreRepository storeRepository,
|
||||
AppService appService,
|
||||
UIInvoiceController invoiceController,
|
||||
LinkGenerator linkGenerator,
|
||||
LightningAddressService lightningAddressService,
|
||||
LightningLikePayoutHandler lightningLikePayoutHandler,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
IPluginHookService pluginHookService,
|
||||
@@ -76,13 +77,13 @@ namespace BTCPayServer
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
_handlers = handlers;
|
||||
_storeRepository = storeRepository;
|
||||
_appService = appService;
|
||||
_invoiceController = invoiceController;
|
||||
_linkGenerator = linkGenerator;
|
||||
_lightningAddressService = lightningAddressService;
|
||||
_lightningLikePayoutHandler = lightningLikePayoutHandler;
|
||||
_pullPaymentHostedService = pullPaymentHostedService;
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_pluginHookService = pluginHookService;
|
||||
@@ -101,10 +102,11 @@ namespace BTCPayServer
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
|
||||
var pmi = PayoutTypes.LN.GetPayoutMethodId(cryptoCode);
|
||||
var paymentMethodId = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var pp = await _pullPaymentHostedService.GetPullPayment(pullPaymentId, true);
|
||||
if (!pp.IsRunning() || !pp.IsSupported(pmi))
|
||||
if (!pp.IsRunning() || !pp.IsSupported(pmi) || !_payoutHandlers.TryGetValue(pmi, out var payoutHandler))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
@@ -126,7 +128,7 @@ namespace BTCPayServer
|
||||
CurrentBalance = LightMoney.FromUnit(remaining, unit),
|
||||
MinWithdrawable =
|
||||
LightMoney.FromUnit(
|
||||
Math.Min(await _lightningLikePayoutHandler.GetMinimumPayoutAmount(pmi, null), remaining),
|
||||
Math.Min(await payoutHandler.GetMinimumPayoutAmount(null), remaining),
|
||||
unit),
|
||||
Tag = "withdrawRequest",
|
||||
Callback = new Uri(Request.GetCurrentUrl()),
|
||||
@@ -147,7 +149,7 @@ namespace BTCPayServer
|
||||
if (result.MinimumAmount < request.MinWithdrawable || result.MinimumAmount > request.MaxWithdrawable)
|
||||
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = $"Payment request was not within bounds ({request.MinWithdrawable.ToUnit(LightMoneyUnit.Satoshi)} - {request.MaxWithdrawable.ToUnit(LightMoneyUnit.Satoshi)} sats)" });
|
||||
var store = await _storeRepository.FindStore(pp.StoreId);
|
||||
var pm = store!.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _handlers);
|
||||
var pm = store!.GetPaymentMethodConfig<LightningPaymentMethodConfig>(paymentMethodId, _handlers);
|
||||
if (pm is null)
|
||||
{
|
||||
return NotFound();
|
||||
@@ -156,7 +158,7 @@ namespace BTCPayServer
|
||||
var claimResponse = await _pullPaymentHostedService.Claim(new ClaimRequest
|
||||
{
|
||||
Destination = new BoltInvoiceClaimDestination(pr, result),
|
||||
PaymentMethodId = pmi,
|
||||
PayoutMethodId = pmi,
|
||||
PullPaymentId = pullPaymentId,
|
||||
StoreId = pp.StoreId,
|
||||
Value = result.MinimumAmount.ToDecimal(unit)
|
||||
@@ -174,7 +176,7 @@ namespace BTCPayServer
|
||||
lightningHandler.CreateLightningClient(pm);
|
||||
var payResult = await UILightningLikePayoutController.TrypayBolt(client,
|
||||
claimResponse.PayoutData.GetBlob(_btcPayNetworkJsonSerializerSettings),
|
||||
claimResponse.PayoutData, result, pmi, cancellationToken);
|
||||
claimResponse.PayoutData, result, payoutHandler.Currency, cancellationToken);
|
||||
|
||||
switch (payResult.Result)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@ using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.NTag424;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@@ -42,7 +43,7 @@ namespace BTCPayServer.Controllers
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayServerEnvironment _env;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
@@ -53,7 +54,7 @@ namespace BTCPayServer.Controllers
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
BTCPayNetworkJsonSerializerSettings serializerSettings,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
StoreRepository storeRepository,
|
||||
BTCPayServerEnvironment env,
|
||||
SettingsRepository settingsRepository)
|
||||
@@ -92,7 +93,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Entity = o,
|
||||
Blob = o.GetBlob(_serializerSettings),
|
||||
ProofBlob = _payoutHandlers.FindPayoutHandler(o.GetPaymentMethodId())?.ParseProof(o)
|
||||
ProofBlob = _payoutHandlers.TryGet(o.GetPayoutMethodId())?.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();
|
||||
@@ -225,29 +226,31 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var ppBlob = pp.GetBlob();
|
||||
var supported = ppBlob.SupportedPaymentMethods;
|
||||
PaymentMethodId paymentMethodId = null;
|
||||
PayoutMethodId payoutMethodId = null;
|
||||
IClaimDestination destination = null;
|
||||
if (string.IsNullOrEmpty(vm.SelectedPaymentMethod))
|
||||
IPayoutHandler payoutHandler = null;
|
||||
if (string.IsNullOrEmpty(vm.SelectedPayoutMethod))
|
||||
{
|
||||
foreach (var pmId in supported)
|
||||
{
|
||||
var handler = _payoutHandlers.FindPayoutHandler(pmId);
|
||||
var handler = _payoutHandlers.TryGet(pmId);
|
||||
(IClaimDestination dst, string err) = handler == null
|
||||
? (null, "No payment handler found for this payment method")
|
||||
: await handler.ParseAndValidateClaimDestination(pmId, vm.Destination, ppBlob, cancellationToken);
|
||||
: await handler.ParseAndValidateClaimDestination(vm.Destination, ppBlob, cancellationToken);
|
||||
if (dst is not null && err is null)
|
||||
{
|
||||
paymentMethodId = pmId;
|
||||
payoutMethodId = pmId;
|
||||
destination = dst;
|
||||
payoutHandler = handler;
|
||||
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;
|
||||
payoutMethodId = supported.FirstOrDefault(id => vm.SelectedPayoutMethod == id.ToString());
|
||||
payoutHandler = payoutMethodId is null ? null : _payoutHandlers.TryGet(payoutMethodId);
|
||||
destination = payoutHandler is null ? null : (await payoutHandler.ParseAndValidateClaimDestination(vm.Destination, ppBlob, cancellationToken)).destination;
|
||||
}
|
||||
|
||||
if (destination is null)
|
||||
@@ -255,8 +258,7 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.AddModelError(nameof(vm.Destination), "Invalid destination or payment method");
|
||||
return await ViewPullPayment(pullPaymentId);
|
||||
}
|
||||
|
||||
var amtError = ClaimRequest.IsPayoutAmountOk(destination, vm.ClaimedAmount == 0 ? null : vm.ClaimedAmount, paymentMethodId.CryptoCode, ppBlob.Currency);
|
||||
var amtError = ClaimRequest.IsPayoutAmountOk(destination, vm.ClaimedAmount == 0 ? null : vm.ClaimedAmount, payoutHandler.Currency, ppBlob.Currency);
|
||||
if (amtError.error is not null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ClaimedAmount), amtError.error);
|
||||
@@ -276,7 +278,7 @@ namespace BTCPayServer.Controllers
|
||||
Destination = destination,
|
||||
PullPaymentId = pullPaymentId,
|
||||
Value = vm.ClaimedAmount,
|
||||
PaymentMethodId = paymentMethodId,
|
||||
PayoutMethodId = payoutMethodId,
|
||||
StoreId = pp.StoreId
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -31,7 +32,7 @@ namespace BTCPayServer.Controllers
|
||||
public class UIStorePullPaymentsController : Controller
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly DisplayFormatter _displayFormatter;
|
||||
private readonly PullPaymentHostedService _pullPaymentService;
|
||||
@@ -50,7 +51,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
public UIStorePullPaymentsController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
DisplayFormatter displayFormatter,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
@@ -74,12 +75,12 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet("stores/{storeId}/pull-payments/new")]
|
||||
[Authorize(Policy = Policies.CanCreateNonApprovedPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> NewPullPayment(string storeId)
|
||||
public IActionResult NewPullPayment(string storeId)
|
||||
{
|
||||
if (CurrentStore is null)
|
||||
return NotFound();
|
||||
|
||||
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
|
||||
var paymentMethods = _payoutHandlers.GetSupportedPayoutMethods(CurrentStore);
|
||||
if (!paymentMethods.Any())
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
@@ -96,7 +97,7 @@ namespace BTCPayServer.Controllers
|
||||
Currency = CurrentStore.GetStoreBlob().DefaultCurrency,
|
||||
CustomCSSLink = "",
|
||||
EmbeddedCSS = "",
|
||||
PaymentMethodItems =
|
||||
PayoutMethodsItem =
|
||||
paymentMethods.Select(id => new SelectListItem(id.ToString(), id.ToString(), true))
|
||||
});
|
||||
}
|
||||
@@ -108,19 +109,19 @@ namespace BTCPayServer.Controllers
|
||||
if (CurrentStore is null)
|
||||
return NotFound();
|
||||
|
||||
var paymentMethodOptions = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
|
||||
model.PaymentMethodItems =
|
||||
var paymentMethodOptions = _payoutHandlers.GetSupportedPayoutMethods(CurrentStore);
|
||||
model.PayoutMethodsItem =
|
||||
paymentMethodOptions.Select(id => new SelectListItem(id.ToString(), id.ToString(), true));
|
||||
model.Name ??= string.Empty;
|
||||
model.Currency = model.Currency?.ToUpperInvariant()?.Trim() ?? String.Empty;
|
||||
model.PaymentMethods ??= new List<string>();
|
||||
if (!model.PaymentMethods.Any())
|
||||
model.PayoutMethods ??= new List<string>();
|
||||
if (!model.PayoutMethods.Any())
|
||||
{
|
||||
// Since we assign all payment methods to be selected by default above we need to update
|
||||
// them here to reflect user's selection so that they can correct their mistake
|
||||
model.PaymentMethodItems =
|
||||
model.PayoutMethodsItem =
|
||||
paymentMethodOptions.Select(id => new SelectListItem(id.ToString(), id.ToString(), false));
|
||||
ModelState.AddModelError(nameof(model.PaymentMethods), "You need at least one payment method");
|
||||
ModelState.AddModelError(nameof(model.PayoutMethods), "You need at least one payout method");
|
||||
}
|
||||
if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null)
|
||||
{
|
||||
@@ -135,10 +136,10 @@ namespace BTCPayServer.Controllers
|
||||
ModelState.AddModelError(nameof(model.Name), "The name should be maximum 50 characters.");
|
||||
}
|
||||
|
||||
var selectedPaymentMethodIds = model.PaymentMethods.Select(PaymentMethodId.Parse).ToArray();
|
||||
if (!selectedPaymentMethodIds.All(id => selectedPaymentMethodIds.Contains(id)))
|
||||
var selectedPaymentMethodIds = model.PayoutMethods.Select(PayoutMethodId.Parse).ToArray();
|
||||
if (!selectedPaymentMethodIds.All(id => paymentMethodOptions.Contains(id)))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.Name), "Not all payment methods are supported");
|
||||
ModelState.AddModelError(nameof(model.Name), "Not all payout methods are supported");
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return View(model);
|
||||
@@ -151,7 +152,7 @@ namespace BTCPayServer.Controllers
|
||||
Amount = model.Amount,
|
||||
Currency = model.Currency,
|
||||
StoreId = storeId,
|
||||
PaymentMethodIds = selectedPaymentMethodIds,
|
||||
PayoutMethodIds = selectedPaymentMethodIds,
|
||||
EmbeddedCSS = model.EmbeddedCSS,
|
||||
CustomCSSLink = model.CustomCSSLink,
|
||||
BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration),
|
||||
@@ -196,7 +197,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
|
||||
var paymentMethods = _payoutHandlers.GetSupportedPayoutMethods(HttpContext.GetStoreData());
|
||||
if (!paymentMethods.Any())
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
@@ -290,11 +291,11 @@ namespace BTCPayServer.Controllers
|
||||
if (vm is null)
|
||||
return NotFound();
|
||||
|
||||
vm.PaymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
|
||||
vm.HasPayoutProcessor = await HasPayoutProcessor(storeId, vm.PaymentMethodId);
|
||||
var paymentMethodId = PaymentMethodId.Parse(vm.PaymentMethodId);
|
||||
vm.PayoutMethods = _payoutHandlers.GetSupportedPayoutMethods(HttpContext.GetStoreData());
|
||||
vm.HasPayoutProcessor = await HasPayoutProcessor(storeId, vm.PayoutMethodId);
|
||||
var payoutMethodId = PayoutMethodId.Parse(vm.PayoutMethodId);
|
||||
var handler = _payoutHandlers
|
||||
.FindPayoutHandler(paymentMethodId);
|
||||
.TryGet(payoutMethodId);
|
||||
var commandState = Enum.Parse<PayoutState>(vm.Command.Split("-").First());
|
||||
var payoutIds = vm.GetSelectedPayouts(commandState);
|
||||
if (payoutIds.Length == 0)
|
||||
@@ -309,7 +310,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
payoutMethodId = payoutMethodId.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -331,7 +332,7 @@ namespace BTCPayServer.Controllers
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts =
|
||||
await GetPayoutsForPaymentMethod(paymentMethodId, ctx, payoutIds, storeId, cancellationToken);
|
||||
await GetPayoutsForPaymentMethod(payoutMethodId, ctx, payoutIds, storeId, cancellationToken);
|
||||
|
||||
var failed = false;
|
||||
for (int i = 0; i < payouts.Count; i++)
|
||||
@@ -391,7 +392,7 @@ namespace BTCPayServer.Controllers
|
||||
case "pay":
|
||||
{
|
||||
if (handler is { })
|
||||
return await handler.InitiatePayment(paymentMethodId, payoutIds);
|
||||
return await handler.InitiatePayment(payoutIds);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Paying via this payment method is not supported",
|
||||
@@ -405,7 +406,7 @@ namespace BTCPayServer.Controllers
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var payouts =
|
||||
await GetPayoutsForPaymentMethod(paymentMethodId, ctx, payoutIds, storeId, cancellationToken);
|
||||
await GetPayoutsForPaymentMethod(payoutMethodId, ctx, payoutIds, storeId, cancellationToken);
|
||||
for (int i = 0; i < payouts.Count; i++)
|
||||
{
|
||||
var payout = payouts[i];
|
||||
@@ -426,7 +427,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
payoutMethodId = payoutMethodId.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -455,11 +456,11 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
payoutMethodId = payoutMethodId.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<List<PayoutData>> GetPayoutsForPaymentMethod(PaymentMethodId paymentMethodId,
|
||||
private static async Task<List<PayoutData>> GetPayoutsForPaymentMethod(PayoutMethodId payoutMethodId,
|
||||
ApplicationDbContext ctx, string[] payoutIds,
|
||||
string storeId, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -469,7 +470,7 @@ namespace BTCPayServer.Controllers
|
||||
IncludeStoreData = true,
|
||||
Stores = new[] { storeId },
|
||||
PayoutIds = payoutIds,
|
||||
PaymentMethods = new[] { paymentMethodId.ToString() }
|
||||
PayoutMethods = new[] { payoutMethodId.ToString() }
|
||||
}, ctx, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -477,10 +478,10 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet("stores/{storeId}/payouts")]
|
||||
[Authorize(Policy = Policies.CanViewPayouts, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Payouts(
|
||||
string storeId, string pullPaymentId, string paymentMethodId, PayoutState payoutState,
|
||||
string storeId, string pullPaymentId, string payoutMethodId, PayoutState payoutState,
|
||||
int skip = 0, int count = 50)
|
||||
{
|
||||
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
|
||||
var paymentMethods = _payoutHandlers.GetSupportedPayoutMethods(HttpContext.GetStoreData());
|
||||
if (!paymentMethods.Any())
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
@@ -491,17 +492,17 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
|
||||
}
|
||||
|
||||
paymentMethodId ??= paymentMethods.First().ToString();
|
||||
payoutMethodId ??= paymentMethods.First().ToString();
|
||||
var vm = this.ParseListQuery(new PayoutsModel
|
||||
{
|
||||
PaymentMethods = paymentMethods,
|
||||
PaymentMethodId = paymentMethodId,
|
||||
PayoutMethods = paymentMethods,
|
||||
PayoutMethodId = payoutMethodId,
|
||||
PullPaymentId = pullPaymentId,
|
||||
PayoutState = payoutState,
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
Payouts = new List<PayoutsModel.PayoutModel>(),
|
||||
HasPayoutProcessor = await HasPayoutProcessor(storeId, paymentMethodId)
|
||||
HasPayoutProcessor = await HasPayoutProcessor(storeId, payoutMethodId)
|
||||
});
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var payoutRequest =
|
||||
@@ -512,15 +513,15 @@ namespace BTCPayServer.Controllers
|
||||
vm.PullPaymentName = (await ctx.PullPayments.FindAsync(pullPaymentId)).GetBlob().Name;
|
||||
}
|
||||
|
||||
if (vm.PaymentMethodId != null)
|
||||
{
|
||||
var pmiStr = vm.PaymentMethodId;
|
||||
payoutRequest = payoutRequest.Where(p => p.PaymentMethodId == pmiStr);
|
||||
}
|
||||
|
||||
vm.PaymentMethodCount = (await payoutRequest.GroupBy(data => data.PaymentMethodId)
|
||||
vm.PayoutMethodCount = (await payoutRequest.GroupBy(data => data.PaymentMethodId)
|
||||
.Select(datas => new { datas.Key, Count = datas.Count() }).ToListAsync())
|
||||
.ToDictionary(datas => datas.Key, arg => arg.Count);
|
||||
|
||||
if (vm.PayoutMethodId != null)
|
||||
{
|
||||
var pmiStr = vm.PayoutMethodId;
|
||||
payoutRequest = payoutRequest.Where(p => p.PaymentMethodId == pmiStr);
|
||||
}
|
||||
vm.PayoutStateCount = payoutRequest.GroupBy(data => data.State)
|
||||
.Select(e => new { e.Key, Count = e.Count() })
|
||||
.ToDictionary(arg => arg.Key, arg => arg.Count);
|
||||
@@ -566,6 +567,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
payoutSourceLink = Url.Action("ViewPullPayment", "UIPullPayment", new { pullPaymentId = item.PullPayment?.Id });
|
||||
}
|
||||
var pCurrency = _payoutHandlers.TryGet(PayoutMethodId.Parse(item.Payout.PaymentMethodId))?.Currency;
|
||||
|
||||
var m = new PayoutsModel.PayoutModel
|
||||
{
|
||||
PullPaymentId = item.PullPayment?.Id,
|
||||
@@ -573,11 +576,11 @@ namespace BTCPayServer.Controllers
|
||||
SourceLink = payoutSourceLink,
|
||||
Date = item.Payout.Date,
|
||||
PayoutId = item.Payout.Id,
|
||||
Amount = _displayFormatter.Currency(payoutBlob.Amount, ppBlob?.Currency ?? PaymentMethodId.Parse(item.Payout.PaymentMethodId).CryptoCode),
|
||||
Amount = _displayFormatter.Currency(payoutBlob.Amount, ppBlob?.Currency ?? pCurrency),
|
||||
Destination = payoutBlob.Destination
|
||||
};
|
||||
var handler = _payoutHandlers
|
||||
.FindPayoutHandler(item.Payout.GetPaymentMethodId());
|
||||
.TryGet(item.Payout.GetPayoutMethodId());
|
||||
var proofBlob = handler?.ParseProof(item.Payout);
|
||||
m.ProofLink = proofBlob?.Link;
|
||||
vm.Payouts.Add(m);
|
||||
@@ -585,12 +588,15 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
private async Task<bool> HasPayoutProcessor(string storeId, string paymentMethodId)
|
||||
private async Task<bool> HasPayoutProcessor(string storeId, PayoutMethodId payoutMethodId)
|
||||
{
|
||||
var pmId = PaymentMethodId.Parse(paymentMethodId);
|
||||
var processors = await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery { Stores = [storeId], PaymentMethods = [pmId] });
|
||||
return _payoutProcessorFactories.Any(factory => factory.GetSupportedPaymentMethods().Contains(pmId)) && processors.Any();
|
||||
new PayoutProcessorService.PayoutProcessorQuery { Stores = [storeId], PayoutMethodIds = [payoutMethodId] });
|
||||
return _payoutProcessorFactories.Any(factory => factory.GetSupportedPayoutMethods().Contains(payoutMethodId)) && processors.Any();
|
||||
}
|
||||
private async Task<bool> HasPayoutProcessor(string storeId, string payoutMethodId)
|
||||
{
|
||||
return PayoutMethodId.TryParse(payoutMethodId, out var pmId) && await HasPayoutProcessor(storeId, pmId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
@@ -761,14 +762,14 @@ namespace BTCPayServer.Controllers
|
||||
CreatePSBTResponse psbtResponse;
|
||||
if (command == "schedule")
|
||||
{
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||
var pmi = PayoutTypes.CHAIN.GetPayoutMethodId(walletId.CryptoCode);
|
||||
var claims =
|
||||
vm.Outputs.Where(output => string.IsNullOrEmpty(output.PayoutId)).Select(output => new ClaimRequest()
|
||||
{
|
||||
Destination = new AddressClaimDestination(
|
||||
BitcoinAddress.Create(output.DestinationAddress, network.NBitcoinNetwork)),
|
||||
Value = output.Amount,
|
||||
PaymentMethodId = pmi,
|
||||
PayoutMethodId = pmi,
|
||||
StoreId = walletId.StoreId,
|
||||
PreApprove = true,
|
||||
}).ToArray();
|
||||
|
||||
@@ -38,13 +38,16 @@ namespace BTCPayServer.Data
|
||||
paymentData.Blob2 = JToken.FromObject(blob, InvoiceDataExtensions.DefaultSerializer).ToString(Newtonsoft.Json.Formatting.None);
|
||||
return paymentData;
|
||||
}
|
||||
|
||||
public static PaymentMethodId GetPaymentMethodId(this PaymentData paymentData)
|
||||
{
|
||||
return PaymentMethodId.Parse(paymentData.Type);
|
||||
}
|
||||
public static PaymentEntity GetBlob(this PaymentData paymentData)
|
||||
{
|
||||
var entity = JToken.Parse(paymentData.Blob2).ToObject<PaymentEntity>(InvoiceDataExtensions.DefaultSerializer) ?? throw new FormatException($"Invalid {nameof(PaymentEntity)}");
|
||||
entity.Status = paymentData.Status!.Value;
|
||||
entity.Currency = paymentData.Currency;
|
||||
entity.PaymentMethodId = PaymentMethodId.Parse(paymentData.Type);
|
||||
entity.PaymentMethodId = GetPaymentMethodId(paymentData);
|
||||
entity.Value = paymentData.Amount!.Value;
|
||||
entity.Id = paymentData.Id;
|
||||
entity.ReceivedTime = paymentData.Created!.Value;
|
||||
|
||||
@@ -14,6 +14,7 @@ using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
@@ -31,10 +32,11 @@ using NewBlockEvent = BTCPayServer.Events.NewBlockEvent;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
|
||||
{
|
||||
public string Currency { get; }
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly PaymentMethodHandlerDictionary _paymentHandlers;
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
@@ -43,9 +45,15 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||
|
||||
PayoutMethodId IHandler<PayoutMethodId>.Id => PayoutMethodId;
|
||||
public PayoutMethodId PayoutMethodId { get; }
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
public BTCPayNetwork Network { get; }
|
||||
public WalletRepository WalletRepository { get; }
|
||||
|
||||
public BitcoinLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
PayoutMethodId payoutMethodId,
|
||||
BTCPayNetwork network,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
WalletRepository walletRepository,
|
||||
ExplorerClientProvider explorerClientProvider,
|
||||
@@ -57,51 +65,46 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
TransactionLinkProviders transactionLinkProviders)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
PayoutMethodId = payoutMethodId;
|
||||
PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
Network = network;
|
||||
_paymentHandlers = handlers;
|
||||
WalletRepository = walletRepository;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_notificationSender = notificationSender;
|
||||
Currency = network.CryptoCode;
|
||||
this.Logs = logs;
|
||||
_eventAggregator = eventAggregator;
|
||||
_transactionLinkProviders = transactionLinkProviders;
|
||||
}
|
||||
|
||||
|
||||
public bool CanHandle(PaymentMethodId paymentMethod)
|
||||
{
|
||||
return _handlers.TryGetValue(paymentMethod, out var h) &&
|
||||
h is BitcoinLikePaymentHandler { Network: { ReadonlyWallet: false } };
|
||||
}
|
||||
|
||||
public async Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData)
|
||||
{
|
||||
var network = _handlers.GetNetwork(claimRequest.PaymentMethodId);
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(network);
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(Network);
|
||||
if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
||||
{
|
||||
|
||||
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
|
||||
await WalletRepository.AddWalletTransactionAttachment(
|
||||
new WalletId(claimRequest.StoreId, network.CryptoCode),
|
||||
new WalletId(claimRequest.StoreId, Network.CryptoCode),
|
||||
bitcoinLikeClaimDestination.Address.ToString(),
|
||||
Attachment.Payout(payoutData.PullPaymentDataId, payoutData.Id), WalletObjectData.Types.Address);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken)
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(string destination, CancellationToken cancellationToken)
|
||||
{
|
||||
var network = _handlers.GetNetwork(paymentMethodId);
|
||||
destination = destination.Trim();
|
||||
try
|
||||
{
|
||||
if (destination.StartsWith($"{network.NBitcoinNetwork.UriScheme}:", StringComparison.OrdinalIgnoreCase))
|
||||
if (destination.StartsWith($"{Network.NBitcoinNetwork.UriScheme}:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult<(IClaimDestination, string)>((new UriClaimDestination(new BitcoinUrlBuilder(destination, network.NBitcoinNetwork)), null));
|
||||
return Task.FromResult<(IClaimDestination, string)>((new UriClaimDestination(new BitcoinUrlBuilder(destination, Network.NBitcoinNetwork)), null));
|
||||
}
|
||||
|
||||
return Task.FromResult<(IClaimDestination, string)>((new AddressClaimDestination(BitcoinAddress.Create(destination, network.NBitcoinNetwork)), null));
|
||||
return Task.FromResult<(IClaimDestination, string)>((new AddressClaimDestination(BitcoinAddress.Create(destination, Network.NBitcoinNetwork)), null));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -119,18 +122,16 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
{
|
||||
if (payout?.Proof is null)
|
||||
return null;
|
||||
var paymentMethodId = payout.GetPaymentMethodId();
|
||||
if (paymentMethodId is null)
|
||||
return null;
|
||||
var cryptoCode = _handlers.TryGetNetwork(paymentMethodId)?.CryptoCode;
|
||||
if (cryptoCode is null)
|
||||
var payoutMethodId = payout.GetPayoutMethodId();
|
||||
if (payoutMethodId is null)
|
||||
return null;
|
||||
var cryptoCode = Network.CryptoCode;
|
||||
ParseProofType(payout.Proof, out var raw, out var proofType);
|
||||
if (proofType == PayoutTransactionOnChainBlob.Type)
|
||||
{
|
||||
|
||||
var res = raw.ToObject<PayoutTransactionOnChainBlob>(
|
||||
JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(cryptoCode)));
|
||||
JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(payoutMethodId)));
|
||||
if (res == null)
|
||||
return null;
|
||||
res.LinkTemplate = _transactionLinkProviders.GetBlockExplorerLink(cryptoCode);
|
||||
@@ -184,10 +185,9 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
}
|
||||
}
|
||||
|
||||
public Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
|
||||
public Task<decimal> GetMinimumPayoutAmount(IClaimDestination claimDestination)
|
||||
{
|
||||
var network = _handlers.TryGetNetwork(paymentMethodId);
|
||||
if (network?
|
||||
if (Network
|
||||
.NBitcoinNetwork?
|
||||
.Consensus?
|
||||
.ConsensusFactory?
|
||||
@@ -226,8 +226,8 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
Stores = new[] { storeId },
|
||||
PayoutIds = payoutIds
|
||||
}, context)).Where(data =>
|
||||
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
|
||||
CanHandle(paymentMethodId))
|
||||
PayoutMethodId.TryParse(data.PaymentMethodId, out var payoutMethodId) &&
|
||||
payoutMethodId == PayoutMethodId)
|
||||
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false);
|
||||
foreach (var valueTuple in payouts)
|
||||
{
|
||||
@@ -251,8 +251,8 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
Stores = new[] { storeId },
|
||||
PayoutIds = payoutIds
|
||||
}, context)).Where(data =>
|
||||
PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) &&
|
||||
CanHandle(paymentMethodId))
|
||||
PayoutMethodId.TryParse(data.PaymentMethodId, out var payoutMethodId) &&
|
||||
payoutMethodId == PayoutMethodId)
|
||||
.Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true);
|
||||
foreach (var valueTuple in payouts)
|
||||
{
|
||||
@@ -273,25 +273,23 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
|
||||
public bool IsSupported(StoreData storeData)
|
||||
{
|
||||
return Task.FromResult(storeData.GetPaymentMethodConfigs<DerivationSchemeSettings>(_handlers, true).Select(c => c.Key));
|
||||
return storeData.GetDerivationSchemeSettings(_paymentHandlers, Network.CryptoCode, true)?.AccountDerivation is not null;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)
|
||||
public async Task<IActionResult> InitiatePayment(string[] payoutIds)
|
||||
{
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var pmi = paymentMethodId;
|
||||
var payouts = await ctx.Payouts.Include(data => data.PullPaymentData)
|
||||
.Where(data => payoutIds.Contains(data.Id)
|
||||
&& pmi.ToString() == data.PaymentMethodId
|
||||
&& PayoutMethodId.ToString() == data.PaymentMethodId
|
||||
&& data.State == PayoutState.AwaitingPayment)
|
||||
.ToListAsync();
|
||||
|
||||
var pullPaymentIds = payouts.Select(data => data.PullPaymentDataId).Distinct().Where(s => s != null).ToArray();
|
||||
var storeId = payouts.First().StoreDataId;
|
||||
var network = _handlers.GetNetwork(paymentMethodId);
|
||||
List<string> bip21 = new List<string>();
|
||||
foreach (var payout in payouts)
|
||||
{
|
||||
@@ -300,9 +298,9 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
continue;
|
||||
}
|
||||
var blob = payout.GetBlob(_jsonSerializerSettings);
|
||||
if (payout.GetPaymentMethodId() != paymentMethodId)
|
||||
if (payout.GetPayoutMethodId() != PayoutMethodId)
|
||||
continue;
|
||||
var claim = await ParseClaimDestination(paymentMethodId, blob.Destination, default);
|
||||
var claim = await ParseClaimDestination(blob.Destination, default);
|
||||
switch (claim.destination)
|
||||
{
|
||||
case UriClaimDestination uriClaimDestination:
|
||||
@@ -312,17 +310,17 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
bip21.Add(newUri.Uri.ToString());
|
||||
break;
|
||||
case AddressClaimDestination addressClaimDestination:
|
||||
var bip21New = network.GenerateBIP21(addressClaimDestination.Address.ToString(), blob.CryptoAmount.Value);
|
||||
var bip21New = Network.GenerateBIP21(addressClaimDestination.Address.ToString(), blob.CryptoAmount.Value);
|
||||
bip21New.QueryParams.Add("payout", payout.Id);
|
||||
bip21.Add(bip21New.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bip21.Any())
|
||||
return new RedirectToActionResult("WalletSend", "UIWallets", new { walletId = new WalletId(storeId, network.CryptoCode).ToString(), bip21 });
|
||||
return new RedirectToActionResult("WalletSend", "UIWallets", new { walletId = new WalletId(storeId, Network.CryptoCode).ToString(), bip21 });
|
||||
return new RedirectToActionResult("Payouts", "UIWallets", new
|
||||
{
|
||||
walletId = new WalletId(storeId, network.CryptoCode).ToString(),
|
||||
walletId = new WalletId(storeId, Network.CryptoCode).ToString(),
|
||||
pullPaymentId = pullPaymentIds.Length == 1 ? pullPaymentIds.First() : null
|
||||
});
|
||||
}
|
||||
@@ -347,7 +345,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
}
|
||||
foreach (var txid in proof.Candidates.ToList())
|
||||
{
|
||||
var explorer = _explorerClientProvider.GetExplorerClient(payout.GetPaymentMethodId().CryptoCode);
|
||||
var explorer = _explorerClientProvider.GetExplorerClient(Network.CryptoCode);
|
||||
var tx = await explorer.GetTransactionAsync(txid);
|
||||
if (tx is null)
|
||||
{
|
||||
@@ -447,7 +445,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
return;
|
||||
|
||||
var derivationSchemeSettings = payout.StoreData
|
||||
.GetDerivationSchemeSettings(_handlers, newTransaction.CryptoCode)?.AccountDerivation;
|
||||
.GetDerivationSchemeSettings(_paymentHandlers, newTransaction.CryptoCode)?.AccountDerivation;
|
||||
if (derivationSchemeSettings is null)
|
||||
return;
|
||||
|
||||
@@ -494,7 +492,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
|
||||
public void SetProofBlob(PayoutData data, PayoutTransactionOnChainBlob blob)
|
||||
{
|
||||
data.SetProofBlob(blob, _jsonSerializerSettings.GetSerializer(data.GetPaymentMethodId().CryptoCode));
|
||||
data.SetProofBlob(blob, _jsonSerializerSettings.GetSerializer(data.GetPayoutMethodId()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,30 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
public interface IPayoutHandler
|
||||
public interface IPayoutHandler : IHandler<PayoutMethodId>
|
||||
{
|
||||
public bool CanHandle(PaymentMethodId paymentMethod);
|
||||
PayoutMethodId IHandler<PayoutMethodId>.Id => PayoutMethodId;
|
||||
string Currency { get; }
|
||||
public PayoutMethodId PayoutMethodId { get; }
|
||||
bool IsSupported(StoreData storeData);
|
||||
public Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData);
|
||||
//Allows payout handler to parse payout destinations on its own
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken);
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(string destination, CancellationToken cancellationToken);
|
||||
public (bool valid, string? error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob? pullPaymentBlob);
|
||||
public async Task<(IClaimDestination? destination, string? error)> ParseAndValidateClaimDestination(PaymentMethodId paymentMethodId, string destination, PullPaymentBlob? pullPaymentBlob, CancellationToken cancellationToken)
|
||||
public async Task<(IClaimDestination? destination, string? error)> ParseAndValidateClaimDestination(string destination, PullPaymentBlob? pullPaymentBlob, CancellationToken cancellationToken)
|
||||
{
|
||||
var res = await ParseClaimDestination(paymentMethodId, destination, cancellationToken);
|
||||
var res = await ParseClaimDestination(destination, cancellationToken);
|
||||
if (res.destination is null)
|
||||
return res;
|
||||
var res2 = ValidateClaimDestination(res.destination, pullPaymentBlob);
|
||||
@@ -34,9 +39,8 @@ public interface IPayoutHandler
|
||||
void StartBackgroundCheck(Action<Type[]> subscribe);
|
||||
//allows you to process events that the main pull payment hosted service is subscribed to
|
||||
Task BackgroundCheck(object o);
|
||||
Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethod, IClaimDestination claimDestination);
|
||||
Task<decimal> GetMinimumPayoutAmount(IClaimDestination claimDestination);
|
||||
Dictionary<PayoutState, List<(string Action, string Text)>> GetPayoutSpecificActions();
|
||||
Task<StatusMessageModel> DoSpecificAction(string action, string[] payoutIds, string storeId);
|
||||
Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData);
|
||||
Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds);
|
||||
Task<IActionResult> InitiatePayment(string[] payoutIds);
|
||||
}
|
||||
|
||||
@@ -6,45 +6,60 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
{
|
||||
public class LightningLikePayoutHandler : IPayoutHandler
|
||||
public class LightningLikePayoutHandler : IPayoutHandler, IHasNetwork
|
||||
{
|
||||
public string Currency { get; }
|
||||
public PayoutMethodId PayoutMethodId { get; }
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
|
||||
private readonly IOptions<LightningNetworkOptions> _options;
|
||||
private PaymentMethodHandlerDictionary _paymentHandlers;
|
||||
|
||||
public BTCPayNetwork Network { get; }
|
||||
|
||||
public const string LightningLikePayoutHandlerOnionNamedClient =
|
||||
nameof(LightningLikePayoutHandlerOnionNamedClient);
|
||||
|
||||
public const string LightningLikePayoutHandlerClearnetNamedClient =
|
||||
nameof(LightningLikePayoutHandlerClearnetNamedClient);
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly UserService _userService;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public LightningLikePayoutHandler(PaymentMethodHandlerDictionary handlers,
|
||||
public LightningLikePayoutHandler(
|
||||
PayoutMethodId payoutMethodId,
|
||||
IOptions<LightningNetworkOptions> options,
|
||||
BTCPayNetwork network,
|
||||
PaymentMethodHandlerDictionary paymentHandlers,
|
||||
IHttpClientFactory httpClientFactory, UserService userService, IAuthorizationService authorizationService)
|
||||
{
|
||||
_handlers = handlers;
|
||||
_paymentHandlers = paymentHandlers;
|
||||
Network = network;
|
||||
PayoutMethodId = payoutMethodId;
|
||||
_options = options;
|
||||
PaymentMethodId = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_userService = userService;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public bool CanHandle(PaymentMethodId paymentMethod)
|
||||
{
|
||||
|
||||
return _handlers.TryGetValue(paymentMethod, out var h) && h is ILightningPaymentHandler;
|
||||
Currency = network.CryptoCode;
|
||||
}
|
||||
|
||||
public Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData)
|
||||
@@ -59,10 +74,9 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
: LightningLikePayoutHandlerClearnetNamedClient);
|
||||
}
|
||||
|
||||
public async Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken)
|
||||
public async Task<(IClaimDestination destination, string error)> ParseClaimDestination(string destination, CancellationToken cancellationToken)
|
||||
{
|
||||
destination = destination.Trim();
|
||||
var network = ((IHasNetwork)_handlers[paymentMethodId]).Network;
|
||||
try
|
||||
{
|
||||
string lnurlTag = null;
|
||||
@@ -92,7 +106,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
}
|
||||
|
||||
var result =
|
||||
BOLT11PaymentRequest.TryParse(destination, out var invoice, network.NBitcoinNetwork)
|
||||
BOLT11PaymentRequest.TryParse(destination, out var invoice, Network.NBitcoinNetwork)
|
||||
? new BoltInvoiceClaimDestination(destination, invoice)
|
||||
: null;
|
||||
|
||||
@@ -144,7 +158,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
|
||||
public Task<decimal> GetMinimumPayoutAmount(IClaimDestination claimDestination)
|
||||
{
|
||||
return Task.FromResult(Money.Satoshis(1).ToDecimal(MoneyUnit.BTC));
|
||||
}
|
||||
@@ -159,34 +173,14 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
return Task.FromResult<StatusMessageModel>(null);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
|
||||
public bool IsSupported(StoreData storeData)
|
||||
{
|
||||
var result = new List<PaymentMethodId>();
|
||||
var methods = storeData.GetPaymentMethodConfigs<LightningPaymentMethodConfig>(_handlers, true);
|
||||
foreach (var m in methods)
|
||||
{
|
||||
if (!m.Value.IsInternalNode)
|
||||
{
|
||||
result.Add(m.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (UserStore storeDataUserStore in storeData.UserStores)
|
||||
{
|
||||
if (!await _userService.IsAdminUser(storeDataUserStore.ApplicationUserId))
|
||||
continue;
|
||||
result.Add(m.Key);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
return storeData.GetPaymentMethodConfig<LightningPaymentMethodConfig>(PaymentMethodId, _paymentHandlers, true)?.IsConfigured(Network, _options.Value) is true;
|
||||
}
|
||||
|
||||
public Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)
|
||||
public Task<IActionResult> InitiatePayment(string[] payoutIds)
|
||||
{
|
||||
var cryptoCode = _handlers.GetNetwork(paymentMethodId).CryptoCode;
|
||||
var cryptoCode = Network.CryptoCode;
|
||||
return Task.FromResult<IActionResult>(new RedirectToActionResult("ConfirmLightningPayout",
|
||||
"UILightningLikePayout", new { cryptoCode, payoutIds }));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@@ -33,7 +34,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly LightningClientFactoryService _lightningClientFactoryService;
|
||||
private readonly IOptions<LightningNetworkOptions> _options;
|
||||
@@ -44,7 +45,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
public UILightningLikePayoutController(ApplicationDbContextFactory applicationDbContextFactory,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
StoreRepository storeRepository,
|
||||
LightningClientFactoryService lightningClientFactoryService,
|
||||
@@ -64,7 +65,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
private async Task<List<PayoutData>> GetPayouts(ApplicationDbContext dbContext, PaymentMethodId pmi,
|
||||
private async Task<List<PayoutData>> GetPayouts(ApplicationDbContext dbContext, PayoutMethodId pmi,
|
||||
string[] payoutIds)
|
||||
{
|
||||
var userId = _userManager.GetUserId(User);
|
||||
@@ -103,7 +104,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
{
|
||||
await SetStoreContext();
|
||||
|
||||
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var pmi = PayoutTypes.LN.GetPayoutMethodId(cryptoCode);
|
||||
|
||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
var payouts = await GetPayouts(ctx, pmi, payoutIds);
|
||||
@@ -127,14 +128,14 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
{
|
||||
await SetStoreContext();
|
||||
|
||||
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var payoutHandler = (LightningLikePayoutHandler)_payoutHandlers.FindPayoutHandler(pmi);
|
||||
var pmi = PayoutTypes.LN.GetPayoutMethodId(cryptoCode);
|
||||
var paymentMethodId = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var payoutHandler = (LightningLikePayoutHandler)_payoutHandlers.TryGet(pmi);
|
||||
|
||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
|
||||
var payouts = (await GetPayouts(ctx, pmi, payoutIds)).GroupBy(data => data.StoreDataId);
|
||||
var results = new List<ResultVM>();
|
||||
var network = ((IHasNetwork)_handlers[pmi]).Network;
|
||||
|
||||
//we group per store and init the transfers by each
|
||||
|
||||
@@ -143,7 +144,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
{
|
||||
var store = payoutDatas.First().StoreData;
|
||||
|
||||
var lightningSupportedPaymentMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _handlers);
|
||||
var lightningSupportedPaymentMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(paymentMethodId, _handlers);
|
||||
|
||||
if (lightningSupportedPaymentMethod.IsInternalNode && !authorizedForInternalNode)
|
||||
{
|
||||
@@ -164,33 +165,33 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
}
|
||||
|
||||
var client =
|
||||
lightningSupportedPaymentMethod.CreateLightningClient(network, _options.Value,
|
||||
lightningSupportedPaymentMethod.CreateLightningClient(payoutHandler.Network, _options.Value,
|
||||
_lightningClientFactoryService);
|
||||
foreach (var payoutData in payoutDatas)
|
||||
{
|
||||
ResultVM result;
|
||||
var blob = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
|
||||
var claim = await payoutHandler.ParseClaimDestination(pmi, blob.Destination, cancellationToken);
|
||||
var claim = await payoutHandler.ParseClaimDestination(blob.Destination, cancellationToken);
|
||||
try
|
||||
{
|
||||
switch (claim.destination)
|
||||
{
|
||||
case LNURLPayClaimDestinaton lnurlPayClaimDestinaton:
|
||||
var lnurlResult = await GetInvoiceFromLNURL(payoutData, payoutHandler, blob,
|
||||
lnurlPayClaimDestinaton, network.NBitcoinNetwork, cancellationToken);
|
||||
lnurlPayClaimDestinaton, payoutHandler.Network.NBitcoinNetwork, cancellationToken);
|
||||
if (lnurlResult.Item2 is not null)
|
||||
{
|
||||
result = lnurlResult.Item2;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await TrypayBolt(client, blob, payoutData, lnurlResult.Item1, pmi, cancellationToken);
|
||||
result = await TrypayBolt(client, blob, payoutData, lnurlResult.Item1, payoutHandler.Currency, cancellationToken);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case BoltInvoiceClaimDestination item1:
|
||||
result = await TrypayBolt(client, blob, payoutData, item1.PaymentRequest, pmi, cancellationToken);
|
||||
result = await TrypayBolt(client, blob, payoutData, item1.PaymentRequest, payoutHandler.Currency, cancellationToken);
|
||||
|
||||
break;
|
||||
default:
|
||||
@@ -276,18 +277,17 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
|
||||
public static async Task<ResultVM> TrypayBolt(
|
||||
ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest,
|
||||
PaymentMethodId pmi, CancellationToken cancellationToken)
|
||||
string payoutCurrency, CancellationToken cancellationToken)
|
||||
{
|
||||
var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
if (boltAmount > payoutBlob.CryptoAmount)
|
||||
{
|
||||
|
||||
payoutData.State = PayoutState.Cancelled;
|
||||
return new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Message = $"The BOLT11 invoice amount ({boltAmount} {pmi.CryptoCode}) did not match the payout's amount ({payoutBlob.CryptoAmount.GetValueOrDefault()} {pmi.CryptoCode})",
|
||||
Message = $"The BOLT11 invoice amount ({boltAmount} {payoutCurrency}) did not match the payout's amount ({payoutBlob.CryptoAmount.GetValueOrDefault()} {payoutCurrency})",
|
||||
Destination = payoutBlob.Destination
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
@@ -28,9 +29,9 @@ namespace BTCPayServer.Data
|
||||
return payout;
|
||||
}
|
||||
|
||||
public static PaymentMethodId GetPaymentMethodId(this PayoutData data)
|
||||
public static PayoutMethodId GetPayoutMethodId(this PayoutData data)
|
||||
{
|
||||
return PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) ? paymentMethodId : null;
|
||||
return PayoutMethodId.TryParse(data.PaymentMethodId, out var pmi) ? pmi : null;
|
||||
}
|
||||
|
||||
public static string GetPayoutSource(this PayoutData data, BTCPayNetworkJsonSerializerSettings jsonSerializerSettings)
|
||||
@@ -44,13 +45,13 @@ namespace BTCPayServer.Data
|
||||
|
||||
public static PayoutBlob GetBlob(this PayoutData data, BTCPayNetworkJsonSerializerSettings serializers)
|
||||
{
|
||||
var result = JsonConvert.DeserializeObject<PayoutBlob>(data.Blob, serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode));
|
||||
var result = JsonConvert.DeserializeObject<PayoutBlob>(data.Blob, serializers.GetSerializer(data.GetPayoutMethodId()));
|
||||
result.Metadata ??= new JObject();
|
||||
return result;
|
||||
}
|
||||
public static void SetBlob(this PayoutData data, PayoutBlob blob, BTCPayNetworkJsonSerializerSettings serializers)
|
||||
{
|
||||
data.Blob = JsonConvert.SerializeObject(blob, serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode)).ToString();
|
||||
data.Blob = JsonConvert.SerializeObject(blob, serializers.GetSerializer(data.GetPayoutMethodId())).ToString();
|
||||
}
|
||||
|
||||
public static JObject GetProofBlobJson(this PayoutData data)
|
||||
@@ -83,10 +84,9 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<List<PaymentMethodId>> GetSupportedPaymentMethods(
|
||||
this IEnumerable<IPayoutHandler> payoutHandlers, StoreData storeData)
|
||||
public static HashSet<PayoutMethodId> GetSupportedPayoutMethods(this PayoutMethodHandlerDictionary payoutHandlers, StoreData storeData)
|
||||
{
|
||||
return (await Task.WhenAll(payoutHandlers.Select(handler => handler.GetSupportedPaymentMethods(storeData)))).SelectMany(ids => ids).ToList();
|
||||
return payoutHandlers.Where(handler => handler.IsSupported(storeData)).Select(p => p.PayoutMethodId).ToHashSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.ComponentModel;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
@@ -27,8 +28,8 @@ namespace BTCPayServer.Data
|
||||
public TimeSpan BOLT11Expiration { get; set; }
|
||||
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(PaymentMethodIdJsonConverter))]
|
||||
public PaymentMethodId[] SupportedPaymentMethods { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(PayoutMethodIdJsonConverter))]
|
||||
public PayoutMethodId[] SupportedPaymentMethods { get; set; }
|
||||
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BTCPayServer.Payouts;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -19,7 +20,7 @@ namespace BTCPayServer.Data
|
||||
data.Blob = JsonConvert.SerializeObject(blob).ToString();
|
||||
}
|
||||
|
||||
public static bool IsSupported(this PullPaymentData data, Payments.PaymentMethodId paymentId)
|
||||
public static bool IsSupported(this PullPaymentData data, PayoutMethodId paymentId)
|
||||
{
|
||||
return data.GetBlob().SupportedPaymentMethods.Contains(paymentId);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ using BTCPayServer.NTag424;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Reporting;
|
||||
@@ -336,13 +337,6 @@ namespace BTCPayServer
|
||||
return transactions.Select(t => t.Result).Where(t => t != null).ToDictionary(o => o.Transaction.GetHash());
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public static IPayoutHandler? FindPayoutHandler(this IEnumerable<IPayoutHandler> handlers, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
return handlers.FirstOrDefault(h => h.CanHandle(paymentMethodId));
|
||||
}
|
||||
#nullable restore
|
||||
|
||||
public static async Task<PSBT> UpdatePSBT(this ExplorerClientProvider explorerClientProvider, DerivationSchemeSettings derivationSchemeSettings, PSBT psbt)
|
||||
{
|
||||
var result = await explorerClientProvider.GetExplorerClient(psbt.Network.NetworkSet.CryptoCode).UpdatePSBTAsync(new UpdatePSBTRequest()
|
||||
@@ -436,19 +430,23 @@ namespace BTCPayServer
|
||||
var h = (BitcoinLikePaymentHandler)handlers[pmi];
|
||||
return h;
|
||||
}
|
||||
public static BTCPayNetwork? TryGetNetwork(this PaymentMethodHandlerDictionary handlers, PaymentMethodId paymentMethodId)
|
||||
public static BTCPayNetwork? TryGetNetwork<TId, THandler>(this HandlersDictionary<TId, THandler> handlers, TId id)
|
||||
where THandler : IHandler<TId>
|
||||
where TId : notnull
|
||||
{
|
||||
if (paymentMethodId is not null &&
|
||||
handlers.TryGetValue(paymentMethodId, out var value) &&
|
||||
if (id is not null &&
|
||||
handlers.TryGetValue(id, out var value) &&
|
||||
value is IHasNetwork { Network: var n })
|
||||
{
|
||||
return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static BTCPayNetwork GetNetwork(this PaymentMethodHandlerDictionary handlers, PaymentMethodId paymentMethodId)
|
||||
public static BTCPayNetwork GetNetwork<TId, THandler>(this HandlersDictionary<TId, THandler> handlers, TId id)
|
||||
where THandler : IHandler<TId>
|
||||
where TId : notnull
|
||||
{
|
||||
return TryGetNetwork(handlers, paymentMethodId) ?? throw new KeyNotFoundException($"Network for {paymentMethodId} is not found");
|
||||
return TryGetNetwork(handlers, id) ?? throw new KeyNotFoundException($"Network for {id} is not found");
|
||||
}
|
||||
public static LightningPaymentMethodConfig? GetLightningConfig(this PaymentMethodHandlerDictionary handlers, Data.StoreData store, BTCPayNetwork network)
|
||||
{
|
||||
|
||||
@@ -38,10 +38,10 @@ namespace BTCPayServer
|
||||
return permissionSet.Contains(permission, storeId);
|
||||
}
|
||||
|
||||
public static DerivationSchemeSettings? GetDerivationSchemeSettings(this StoreData store, PaymentMethodHandlerDictionary handlers, string cryptoCode)
|
||||
public static DerivationSchemeSettings? GetDerivationSchemeSettings(this StoreData store, PaymentMethodHandlerDictionary handlers, string cryptoCode, bool onlyEnabled = false)
|
||||
{
|
||||
var pmi = Payments.PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
return store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||
return store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers, onlyEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
48
BTCPayServer/HandlersDictionary.cs
Normal file
48
BTCPayServer/HandlersDictionary.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
#nullable enable
|
||||
using BTCPayServer.Payments;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using Amazon.Runtime.Internal.Transform;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public interface IHandler<TId> { TId Id { get; } }
|
||||
public class HandlersDictionary<TId, THandler> : IEnumerable<THandler> where THandler: IHandler<TId>
|
||||
where TId: notnull
|
||||
{
|
||||
public HandlersDictionary(IEnumerable<THandler> handlers)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
_mappedHandlers.Add(handler.Id, handler);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<TId, THandler> _mappedHandlers =
|
||||
new Dictionary<TId, THandler>();
|
||||
public bool TryGetValue(TId id, [MaybeNullWhen(false)] out THandler value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
return _mappedHandlers.TryGetValue(id, out value);
|
||||
}
|
||||
public THandler? TryGet(TId id)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
_mappedHandlers.TryGetValue(id, out var value);
|
||||
return value;
|
||||
}
|
||||
public bool Support(TId id) => _mappedHandlers.ContainsKey(id);
|
||||
public THandler this[TId index] => _mappedHandlers[index];
|
||||
public IEnumerator<THandler> GetEnumerator()
|
||||
{
|
||||
return _mappedHandlers.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@@ -39,7 +40,7 @@ namespace BTCPayServer.HostedServices
|
||||
public string Currency { get; set; }
|
||||
public string CustomCSSLink { get; set; }
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public PaymentMethodId[] PaymentMethodIds { get; set; }
|
||||
public PayoutMethodId[] PayoutMethodIds { get; set; }
|
||||
public TimeSpan? Period { get; set; }
|
||||
public bool AutoApproveClaims { get; set; }
|
||||
public TimeSpan? BOLT11Expiration { get; set; }
|
||||
@@ -131,7 +132,7 @@ namespace BTCPayServer.HostedServices
|
||||
Currency = create.Currency,
|
||||
Limit = create.Amount,
|
||||
Period = o.Period is long periodSeconds ? (TimeSpan?)TimeSpan.FromSeconds(periodSeconds) : null,
|
||||
SupportedPaymentMethods = create.PaymentMethodIds,
|
||||
SupportedPaymentMethods = create.PayoutMethodIds,
|
||||
AutoApproveClaims = create.AutoApproveClaims,
|
||||
View = new PullPaymentBlob.PullPaymentView()
|
||||
{
|
||||
@@ -153,7 +154,7 @@ namespace BTCPayServer.HostedServices
|
||||
public PayoutState[] States { get; set; }
|
||||
public string[] PullPayments { get; set; }
|
||||
public string[] PayoutIds { get; set; }
|
||||
public string[] PaymentMethods { get; set; }
|
||||
public string[] PayoutMethods { get; set; }
|
||||
public string[] Stores { get; set; }
|
||||
public bool IncludeArchived { get; set; }
|
||||
public bool IncludeStoreData { get; set; }
|
||||
@@ -203,16 +204,16 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
|
||||
if (payoutQuery.PaymentMethods is not null)
|
||||
if (payoutQuery.PayoutMethods is not null)
|
||||
{
|
||||
if (payoutQuery.PaymentMethods.Length == 1)
|
||||
if (payoutQuery.PayoutMethods.Length == 1)
|
||||
{
|
||||
var pm = payoutQuery.PaymentMethods[0];
|
||||
var pm = payoutQuery.PayoutMethods[0];
|
||||
query = query.Where(data => pm == data.PaymentMethodId);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = query.Where(data => payoutQuery.PaymentMethods.Contains(data.PaymentMethodId));
|
||||
query = query.Where(data => payoutQuery.PayoutMethods.Contains(data.PaymentMethodId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,10 +285,9 @@ namespace BTCPayServer.HostedServices
|
||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
PayoutMethodHandlerDictionary handlers,
|
||||
NotificationSender notificationSender,
|
||||
RateFetcher rateFetcher,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
ILogger<PullPaymentHostedService> logger,
|
||||
Logs logs,
|
||||
DisplayFormatter displayFormatter,
|
||||
@@ -300,7 +300,6 @@ namespace BTCPayServer.HostedServices
|
||||
_handlers = handlers;
|
||||
_notificationSender = notificationSender;
|
||||
_rateFetcher = rateFetcher;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
_logger = logger;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_displayFormatter = displayFormatter;
|
||||
@@ -311,10 +310,9 @@ namespace BTCPayServer.HostedServices
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly PayoutMethodHandlerDictionary _handlers;
|
||||
private readonly NotificationSender _notificationSender;
|
||||
private readonly RateFetcher _rateFetcher;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly ILogger<PullPaymentHostedService> _logger;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly DisplayFormatter _displayFormatter;
|
||||
@@ -323,7 +321,7 @@ namespace BTCPayServer.HostedServices
|
||||
internal override Task[] InitializeTasks()
|
||||
{
|
||||
_Channel = Channel.CreateUnbounded<object>();
|
||||
foreach (IPayoutHandler payoutHandler in _payoutHandlers)
|
||||
foreach (IPayoutHandler payoutHandler in _handlers)
|
||||
{
|
||||
payoutHandler.StartBackgroundCheck(Subscribe);
|
||||
}
|
||||
@@ -363,7 +361,7 @@ namespace BTCPayServer.HostedServices
|
||||
await HandleMarkPaid(paid);
|
||||
}
|
||||
|
||||
foreach (IPayoutHandler payoutHandler in _payoutHandlers)
|
||||
foreach (IPayoutHandler payoutHandler in _handlers)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -380,7 +378,7 @@ namespace BTCPayServer.HostedServices
|
||||
public bool SupportsLNURL(PullPaymentBlob blob)
|
||||
{
|
||||
var pms = blob.SupportedPaymentMethods.FirstOrDefault(id =>
|
||||
PaymentTypes.LN.GetPaymentMethodId(_networkProvider.DefaultNetwork.CryptoCode)
|
||||
PayoutTypes.LN.GetPayoutMethodId(_networkProvider.DefaultNetwork.CryptoCode)
|
||||
== id);
|
||||
return pms is not null && _lnurlSupportedCurrencies.Contains(blob.Currency);
|
||||
}
|
||||
@@ -388,7 +386,7 @@ namespace BTCPayServer.HostedServices
|
||||
public Task<RateResult> GetRate(PayoutData payout, string explicitRateRule, CancellationToken cancellationToken)
|
||||
{
|
||||
var ppBlob = payout.PullPaymentData?.GetBlob();
|
||||
var payoutPaymentMethod = payout.GetPaymentMethodId();
|
||||
var payoutPaymentMethod = payout.GetPayoutMethodId();
|
||||
var cryptoCode = _handlers.TryGetNetwork(payoutPaymentMethod)?.NBXplorerNetwork.CryptoCode;
|
||||
var currencyPair = new Rating.CurrencyPair(cryptoCode,
|
||||
ppBlob?.Currency ?? cryptoCode);
|
||||
@@ -450,7 +448,7 @@ namespace BTCPayServer.HostedServices
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PaymentMethodId.TryParse(payout.PaymentMethodId, out var paymentMethod))
|
||||
if (!PayoutMethodId.TryParse(payout.PaymentMethodId, out var paymentMethod))
|
||||
{
|
||||
req.Completion.SetResult(new PayoutApproval.ApprovalResult(PayoutApproval.Result.NotFound, null));
|
||||
return;
|
||||
@@ -468,12 +466,12 @@ namespace BTCPayServer.HostedServices
|
||||
cryptoCode == payout.PullPaymentData.GetBlob().Currency)
|
||||
req.Rate = 1.0m;
|
||||
var cryptoAmount = payoutBlob.Amount / req.Rate;
|
||||
var payoutHandler = _payoutHandlers.FindPayoutHandler(paymentMethod);
|
||||
var payoutHandler = _handlers.TryGet(paymentMethod);
|
||||
if (payoutHandler is null)
|
||||
throw new InvalidOperationException($"No payout handler for {paymentMethod}");
|
||||
var dest = await payoutHandler.ParseClaimDestination(paymentMethod, payoutBlob.Destination, default);
|
||||
var dest = await payoutHandler.ParseClaimDestination(payoutBlob.Destination, default);
|
||||
decimal minimumCryptoAmount =
|
||||
await payoutHandler.GetMinimumPayoutAmount(paymentMethod, dest.destination);
|
||||
await payoutHandler.GetMinimumPayoutAmount(dest.destination);
|
||||
if (cryptoAmount < minimumCryptoAmount)
|
||||
{
|
||||
req.Completion.TrySetResult(new PayoutApproval.ApprovalResult(PayoutApproval.Result.TooLowAmount, null));
|
||||
@@ -572,7 +570,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
ppBlob = pp.GetBlob();
|
||||
|
||||
if (!ppBlob.SupportedPaymentMethods.Contains(req.ClaimRequest.PaymentMethodId))
|
||||
if (!ppBlob.SupportedPaymentMethods.Contains(req.ClaimRequest.PayoutMethodId))
|
||||
{
|
||||
req.Completion.TrySetResult(
|
||||
new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.PaymentMethodNotSupported));
|
||||
@@ -581,7 +579,7 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
|
||||
var payoutHandler =
|
||||
_payoutHandlers.FindPayoutHandler(req.ClaimRequest.PaymentMethodId);
|
||||
_handlers.TryGet(req.ClaimRequest.PayoutMethodId);
|
||||
if (payoutHandler is null)
|
||||
{
|
||||
req.Completion.TrySetResult(
|
||||
@@ -602,8 +600,7 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
|
||||
if (req.ClaimRequest.Value <
|
||||
await payoutHandler.GetMinimumPayoutAmount(req.ClaimRequest.PaymentMethodId,
|
||||
req.ClaimRequest.Destination))
|
||||
await payoutHandler.GetMinimumPayoutAmount(req.ClaimRequest.Destination))
|
||||
{
|
||||
req.Completion.TrySetResult(new ClaimRequest.ClaimResponse(ClaimRequest.ClaimResult.AmountTooLow));
|
||||
return;
|
||||
@@ -636,7 +633,7 @@ namespace BTCPayServer.HostedServices
|
||||
Date = now,
|
||||
State = PayoutState.AwaitingApproval,
|
||||
PullPaymentDataId = req.ClaimRequest.PullPaymentId,
|
||||
PaymentMethodId = req.ClaimRequest.PaymentMethodId.ToString(),
|
||||
PaymentMethodId = req.ClaimRequest.PayoutMethodId.ToString(),
|
||||
Destination = req.ClaimRequest.Destination.Id,
|
||||
StoreDataId = req.ClaimRequest.StoreId ?? pp?.StoreId
|
||||
};
|
||||
@@ -683,7 +680,7 @@ namespace BTCPayServer.HostedServices
|
||||
new PayoutNotification()
|
||||
{
|
||||
StoreId = payout.StoreDataId,
|
||||
Currency = ppBlob?.Currency ?? _handlers.TryGetNetwork(req.ClaimRequest.PaymentMethodId)?.NBXplorerNetwork.CryptoCode,
|
||||
Currency = ppBlob?.Currency ?? _handlers.TryGetNetwork(req.ClaimRequest.PayoutMethodId)?.NBXplorerNetwork.CryptoCode,
|
||||
Status = payout.State,
|
||||
PaymentMethod = payout.PaymentMethodId,
|
||||
PayoutId = payout.Id
|
||||
@@ -961,7 +958,7 @@ namespace BTCPayServer.HostedServices
|
||||
PaymentMethodNotSupported,
|
||||
}
|
||||
|
||||
public PaymentMethodId PaymentMethodId { get; set; }
|
||||
public PayoutMethodId PayoutMethodId { get; set; }
|
||||
public string PullPaymentId { get; set; }
|
||||
public decimal? Value { get; set; }
|
||||
public IClaimDestination Destination { get; set; }
|
||||
|
||||
@@ -70,6 +70,8 @@ using BTCPayServer.Services.Reporting;
|
||||
using BTCPayServer.Services.WalletFileParsing;
|
||||
using BTCPayServer.Payments.LNURLPay;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Payouts;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -369,10 +371,6 @@ namespace BTCPayServer.Hosting
|
||||
services.AddReportProvider<PayoutsReportProvider>();
|
||||
services.AddReportProvider<LegacyInvoiceExportReportProvider>();
|
||||
services.AddWebhooks();
|
||||
services.AddSingleton<BitcoinLikePayoutHandler>();
|
||||
services.AddSingleton<IPayoutHandler>(provider => provider.GetRequiredService<BitcoinLikePayoutHandler>());
|
||||
services.AddSingleton<IPayoutHandler>(provider => provider.GetRequiredService<LightningLikePayoutHandler>());
|
||||
services.AddSingleton<LightningLikePayoutHandler>();
|
||||
|
||||
services.AddSingleton<Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension>>(o =>
|
||||
o.GetRequiredService<IEnumerable<IPaymentMethodBitpayAPIExtension>>().ToDictionary(o => o.PaymentMethodId, o => o));
|
||||
@@ -397,7 +395,9 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
||||
|
||||
services.AddSingleton<PaymentMethodHandlerDictionary>();
|
||||
services.AddSingleton<PaymentMethodViewProvider>();
|
||||
|
||||
|
||||
services.AddSingleton<PayoutMethodHandlerDictionary>();
|
||||
|
||||
services.AddSingleton<NotificationManager>();
|
||||
services.AddScoped<NotificationSender>();
|
||||
|
||||
@@ -580,6 +580,13 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
||||
(IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodBitpayAPIExtension), new object[] { pmi }));
|
||||
services.AddSingleton<IPaymentMethodViewExtension>(provider =>
|
||||
(IPaymentMethodViewExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodViewExtension), new object[] { pmi }));
|
||||
|
||||
if (!network.ReadonlyWallet && network.WalletSupported)
|
||||
{
|
||||
var payoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId(network.CryptoCode);
|
||||
services.AddSingleton<IPayoutHandler>(provider =>
|
||||
(IPayoutHandler)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinLikePayoutHandler), new object[] { payoutMethodId, network }));
|
||||
}
|
||||
}
|
||||
if (network.NBitcoinNetwork.Consensus.SupportSegwit && network.SupportLightning)
|
||||
{
|
||||
@@ -596,6 +603,9 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
||||
(IPaymentMethodViewExtension)ActivatorUtilities.CreateInstance(provider, typeof(LightningPaymentMethodViewExtension), new object[] { pmi }));
|
||||
services.AddSingleton<IPaymentMethodBitpayAPIExtension>(provider =>
|
||||
(IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(LightningPaymentMethodBitpayAPIExtension), new object[] { pmi }));
|
||||
var payoutMethodId = PayoutTypes.LN.GetPayoutMethodId(network.CryptoCode);
|
||||
services.AddSingleton<IPayoutHandler>(provider =>
|
||||
(IPayoutHandler)ActivatorUtilities.CreateInstance(provider, typeof(LightningLikePayoutHandler), new object[] { payoutMethodId, network }));
|
||||
}
|
||||
// LNURL
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Plugins.Crowdfund;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
@@ -47,7 +48,7 @@ namespace BTCPayServer.Hosting
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly SettingsRepository _Settings;
|
||||
private readonly AppService _appService;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly LightningAddressService _lightningAddressService;
|
||||
private readonly ILogger<MigrationStartupTask> _logger;
|
||||
@@ -62,7 +63,7 @@ namespace BTCPayServer.Hosting
|
||||
IOptions<LightningNetworkOptions> lightningOptions,
|
||||
SettingsRepository settingsRepository,
|
||||
AppService appService,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
LightningAddressService lightningAddressService,
|
||||
ILogger<MigrationStartupTask> logger,
|
||||
@@ -238,7 +239,7 @@ namespace BTCPayServer.Hosting
|
||||
var processors = await ctx.PayoutProcessors.ToArrayAsync();
|
||||
foreach (var processor in processors)
|
||||
{
|
||||
processor.PaymentMethod = processor.GetPaymentMethodId().ToString();
|
||||
processor.PaymentMethod = processor.GetPayoutMethodId().ToString();
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
@@ -641,18 +642,18 @@ WHERE cte.""Id""=p.""Id""
|
||||
await using var ctx = _DBContextFactory.CreateContext();
|
||||
foreach (var payoutData in await ctx.Payouts.AsQueryable().ToArrayAsync())
|
||||
{
|
||||
var pmi = payoutData.GetPaymentMethodId();
|
||||
var pmi = payoutData.GetPayoutMethodId();
|
||||
if (pmi is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var handler = _payoutHandlers
|
||||
.FindPayoutHandler(pmi);
|
||||
.TryGet(pmi);
|
||||
if (handler is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var claim = await handler?.ParseClaimDestination(pmi, payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination, default);
|
||||
var claim = await handler?.ParseClaimDestination(payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination, default);
|
||||
payoutData.Destination = claim.destination?.Id;
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
29
BTCPayServer/JsonConverters/PayoutMethodIdJsonConverter.cs
Normal file
29
BTCPayServer/JsonConverters/PayoutMethodIdJsonConverter.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.JsonConverters
|
||||
{
|
||||
public class PayoutMethodIdJsonConverter : JsonConverter<PayoutMethodId>
|
||||
{
|
||||
public override PayoutMethodId ReadJson(JsonReader reader, Type objectType, PayoutMethodId existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (reader.TokenType != JsonToken.String)
|
||||
throw new JsonObjectException("A payment method id should be a string", reader);
|
||||
if (PayoutMethodId.TryParse((string)reader.Value, out var result))
|
||||
return result;
|
||||
return null;
|
||||
// We need to do this gracefully as we have removed support for a payment type in the past which will throw here on your store each time it is loaded.
|
||||
// throw new JsonObjectException($"Invalid payment method id ({(string)reader.Value})", reader);
|
||||
}
|
||||
public override void WriteJson(JsonWriter writer, PayoutMethodId value, JsonSerializer serializer)
|
||||
{
|
||||
if (value != null)
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string Title { get; set; }
|
||||
public SelectList AvailablePaymentMethods { get; set; }
|
||||
|
||||
[Display(Name = "Select the payment method used for refund")]
|
||||
public string SelectedPaymentMethod { get; set; }
|
||||
[Display(Name = "Select the payout method used for refund")]
|
||||
public string SelectedPayoutMethod { get; set; }
|
||||
public RefundSteps RefundStep { get; set; }
|
||||
public string SelectedRefundOption { get; set; }
|
||||
public decimal CryptoAmountNow { get; set; }
|
||||
|
||||
@@ -5,6 +5,7 @@ using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using PullPaymentData = BTCPayServer.Data.PullPaymentData;
|
||||
|
||||
@@ -21,9 +22,9 @@ namespace BTCPayServer.Models
|
||||
Id = data.Id;
|
||||
StoreId = data.StoreId;
|
||||
var blob = data.GetBlob();
|
||||
PaymentMethods = blob.SupportedPaymentMethods;
|
||||
BitcoinOnly = blob.SupportedPaymentMethods.All(p => p.CryptoCode == "BTC");
|
||||
SelectedPaymentMethod = PaymentMethods.First().ToString();
|
||||
PayoutMethodIds = blob.SupportedPaymentMethods;
|
||||
BitcoinOnly = blob.SupportedPaymentMethods.All(p => p == PayoutTypes.CHAIN.GetPayoutMethodId("BTC") || p == PayoutTypes.LN.GetPayoutMethodId("BTC"));
|
||||
SelectedPayoutMethod = PayoutMethodIds.First().ToString();
|
||||
Archived = data.Archived;
|
||||
AutoApprove = blob.AutoApproveClaims;
|
||||
Title = blob.View.Title;
|
||||
@@ -67,9 +68,9 @@ namespace BTCPayServer.Models
|
||||
|
||||
public string StoreId { get; set; }
|
||||
|
||||
public string SelectedPaymentMethod { get; set; }
|
||||
public string SelectedPayoutMethod { get; set; }
|
||||
|
||||
public PaymentMethodId[] PaymentMethods { get; set; }
|
||||
public PayoutMethodId[] PayoutMethodIds { get; set; }
|
||||
|
||||
public string SetupDeepLink { get; set; }
|
||||
public string ResetDeepLink { get; set; }
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
|
||||
namespace BTCPayServer.Models.WalletViewModels
|
||||
{
|
||||
@@ -11,12 +12,12 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public string PullPaymentId { get; set; }
|
||||
public string Command { get; set; }
|
||||
public Dictionary<PayoutState, int> PayoutStateCount { get; set; }
|
||||
public Dictionary<string, int> PaymentMethodCount { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
public Dictionary<string, int> PayoutMethodCount { get; set; }
|
||||
public string PayoutMethodId { get; set; }
|
||||
|
||||
public List<PayoutModel> Payouts { get; set; }
|
||||
public override int CurrentPageCount => Payouts.Count;
|
||||
public IEnumerable<PaymentMethodId> PaymentMethods { get; set; }
|
||||
public IEnumerable<PayoutMethodId> PayoutMethods { get; set; }
|
||||
public PayoutState PayoutState { get; set; }
|
||||
public string PullPaymentName { get; set; }
|
||||
public bool HasPayoutProcessor { get; set; }
|
||||
|
||||
@@ -62,9 +62,9 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
[Display(Name = "Custom CSS Code")]
|
||||
public string EmbeddedCSS { get; set; }
|
||||
|
||||
[Display(Name = "Payment Methods")]
|
||||
public IEnumerable<string> PaymentMethods { get; set; }
|
||||
public IEnumerable<SelectListItem> PaymentMethodItems { get; set; }
|
||||
[Display(Name = "Payout Methods")]
|
||||
public IEnumerable<string> PayoutMethods { get; set; }
|
||||
public IEnumerable<SelectListItem> PayoutMethodsItem { get; set; }
|
||||
[Display(Name = "Minimum acceptable expiration time for BOLT11 for refunds")]
|
||||
[Range(0, 365 * 10)]
|
||||
public long BOLT11Expiration { get; set; } = 30;
|
||||
|
||||
@@ -31,8 +31,9 @@ namespace BTCPayServer.Payments
|
||||
/// <summary>
|
||||
/// This class customize invoice creation by the creation of payment details for the PaymentMethod during invoice creation
|
||||
/// </summary>
|
||||
public interface IPaymentMethodHandler
|
||||
public interface IPaymentMethodHandler : IHandler<PaymentMethodId>
|
||||
{
|
||||
PaymentMethodId IHandler<PaymentMethodId>.Id => PaymentMethodId;
|
||||
PaymentMethodId PaymentMethodId { get; }
|
||||
/// <summary>
|
||||
/// The creation of the prompt details and prompt data
|
||||
|
||||
@@ -7,7 +7,10 @@ namespace BTCPayServer.Payments.Lightning
|
||||
public static class LightningExtensions
|
||||
{
|
||||
|
||||
|
||||
public static bool IsConfigured(this LightningPaymentMethodConfig supportedPaymentMethod, BTCPayNetwork network, LightningNetworkOptions options)
|
||||
{
|
||||
return supportedPaymentMethod.GetExternalLightningUrl() is not null || (supportedPaymentMethod.IsInternalNode && options.InternalLightningByCryptoCode.ContainsKey(network.CryptoCode));
|
||||
}
|
||||
public static ILightningClient CreateLightningClient(this LightningPaymentMethodConfig supportedPaymentMethod, BTCPayNetwork network, LightningNetworkOptions options, LightningClientFactoryService lightningClientFactory)
|
||||
{
|
||||
var external = supportedPaymentMethod.GetExternalLightningUrl();
|
||||
@@ -17,7 +20,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!options.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode, out var connectionString))
|
||||
if (!supportedPaymentMethod.IsInternalNode || !options.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode, out var connectionString))
|
||||
throw new PaymentMethodUnavailableException("No internal node configured");
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Payouts.LightningLike;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@@ -22,10 +23,10 @@ public class LightningPendingPayoutListener : BaseAsyncService
|
||||
private readonly LightningClientFactoryService _lightningClientFactoryService;
|
||||
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly LightningLikePayoutHandler _lightningLikePayoutHandler;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly IOptions<LightningNetworkOptions> _options;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
public static int SecondsDelay = 60 * 10;
|
||||
|
||||
@@ -33,21 +34,21 @@ public class LightningPendingPayoutListener : BaseAsyncService
|
||||
LightningClientFactoryService lightningClientFactoryService,
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
LightningLikePayoutHandler lightningLikePayoutHandler,
|
||||
StoreRepository storeRepository,
|
||||
IOptions<LightningNetworkOptions> options,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
ILogger<LightningPendingPayoutListener> logger) : base(logger)
|
||||
{
|
||||
_lightningClientFactoryService = lightningClientFactoryService;
|
||||
_applicationDbContextFactory = applicationDbContextFactory;
|
||||
_pullPaymentHostedService = pullPaymentHostedService;
|
||||
_lightningLikePayoutHandler = lightningLikePayoutHandler;
|
||||
_storeRepository = storeRepository;
|
||||
_options = options;
|
||||
|
||||
_networkProvider = networkProvider;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ public class LightningPendingPayoutListener : BaseAsyncService
|
||||
new PullPaymentHostedService.PayoutQuery()
|
||||
{
|
||||
States = new PayoutState[] { PayoutState.InProgress },
|
||||
PaymentMethods = networks.Keys.Select(id => id.ToString()).ToArray()
|
||||
PayoutMethods = networks.Keys.Select(id => id.ToString()).ToArray()
|
||||
}, context);
|
||||
var storeIds = payouts.Select(data => data.StoreDataId).Distinct();
|
||||
var stores = (await Task.WhenAll(storeIds.Select(_storeRepository.FindStore)))
|
||||
@@ -100,7 +101,8 @@ public class LightningPendingPayoutListener : BaseAsyncService
|
||||
pm.CreateLightningClient(networks[pmi], _options.Value, _lightningClientFactoryService);
|
||||
foreach (PayoutData payoutData in payoutByStoreByPaymentMethod)
|
||||
{
|
||||
var proof = _lightningLikePayoutHandler.ParseProof(payoutData);
|
||||
var handler = _payoutHandlers.TryGet(payoutData.GetPayoutMethodId());
|
||||
var proof = handler is null ? null : handler.ParseProof(payoutData);
|
||||
switch (proof)
|
||||
{
|
||||
case null:
|
||||
|
||||
@@ -4,8 +4,6 @@ using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
@@ -15,24 +13,53 @@ namespace BTCPayServer.Payments
|
||||
/// </summary>
|
||||
public class PaymentMethodId
|
||||
{
|
||||
public PaymentMethodId? FindNearest(IEnumerable<PaymentMethodId> others)
|
||||
public T? FindNearest<T>(IEnumerable<T> others)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(others);
|
||||
return others.FirstOrDefault(f => f == this) ??
|
||||
others.FirstOrDefault(f => f._CryptoCode == _CryptoCode);
|
||||
return
|
||||
GetSimilarities([this], others)
|
||||
.OrderByDescending(o => o.similarity)
|
||||
.Select(o => o.b)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
public PaymentMethodId(string cryptoCode, string paymentType)
|
||||
|
||||
/// <summary>
|
||||
/// Returns the carthesian product of the two enumerables with the similarity between each pair's strings.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="U"></typeparam>
|
||||
/// <param name="aItems"></param>
|
||||
/// <param name="bItems"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<(T a, U b, int similarity)> GetSimilarities<T, U>(IEnumerable<T> aItems, IEnumerable<U> bItems)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(cryptoCode);
|
||||
ArgumentNullException.ThrowIfNull(paymentType);
|
||||
_CryptoCode = cryptoCode.ToUpperInvariant();
|
||||
_Id = $"{_CryptoCode}-{paymentType}";
|
||||
return from a in aItems
|
||||
from b in bItems
|
||||
select (a, b, CalculateDistance(a.ToString()!, b.ToString()!));
|
||||
}
|
||||
|
||||
private static int CalculateDistance(string a, string b)
|
||||
{
|
||||
int similarity = 0;
|
||||
for (int i = 0; i < Math.Min(a.Length, b.Length); i++)
|
||||
{
|
||||
if (a[i] == b[i])
|
||||
similarity++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (a.Length == b.Length)
|
||||
similarity++;
|
||||
return similarity;
|
||||
}
|
||||
|
||||
public PaymentMethodId(string id)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
_Id = id;
|
||||
}
|
||||
|
||||
string _Id;
|
||||
string _CryptoCode;
|
||||
public string CryptoCode => _CryptoCode;
|
||||
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
@@ -93,6 +120,11 @@ namespace BTCPayServer.Payments
|
||||
{
|
||||
str ??= "";
|
||||
str = str.Trim();
|
||||
if (str.Length == 0)
|
||||
{
|
||||
paymentMethodId = null;
|
||||
return false;
|
||||
}
|
||||
paymentMethodId = null;
|
||||
var parts = str.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
@@ -101,7 +133,7 @@ namespace BTCPayServer.Payments
|
||||
{
|
||||
if (LegacySupportedCryptos.Contains(cryptoCode))
|
||||
{
|
||||
paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode.ToUpperInvariant());
|
||||
paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -118,13 +150,14 @@ namespace BTCPayServer.Payments
|
||||
paymentMethodId = type.GetPaymentMethodId(cryptoCode);
|
||||
return true;
|
||||
}
|
||||
paymentMethodId = new PaymentMethodId(cryptoCode, paymentType);
|
||||
paymentMethodId = new PaymentMethodId($"{cryptoCode.ToUpperInvariant()}-{paymentType}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
paymentMethodId = new PaymentMethodId(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static PaymentType? GetPaymentType(string paymentType)
|
||||
|
||||
@@ -19,6 +19,10 @@ namespace BTCPayServer.Payments
|
||||
{
|
||||
_paymentType = paymentType;
|
||||
}
|
||||
public PaymentMethodId GetPaymentMethodId(string cryptoCode) => new (cryptoCode, _paymentType);
|
||||
public PaymentMethodId GetPaymentMethodId(string cryptoCode) => new ($"{cryptoCode.ToUpperInvariant()}-{_paymentType}");
|
||||
public override string ToString()
|
||||
{
|
||||
return _paymentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
@@ -39,25 +40,28 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
|
||||
protected readonly StoreRepository _storeRepository;
|
||||
protected readonly PayoutProcessorData PayoutProcessorSettings;
|
||||
protected readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly PaymentMethodHandlerDictionary _paymentHandlers;
|
||||
protected readonly PayoutMethodId PayoutMethodId;
|
||||
protected readonly PaymentMethodId PaymentMethodId;
|
||||
private readonly IPluginHookService _pluginHookService;
|
||||
protected readonly EventAggregator _eventAggregator;
|
||||
|
||||
protected BaseAutomatedPayoutProcessor(
|
||||
PaymentMethodId paymentMethodId,
|
||||
ILoggerFactory logger,
|
||||
StoreRepository storeRepository,
|
||||
PayoutProcessorData payoutProcessorSettings,
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
PaymentMethodHandlerDictionary paymentHandlers,
|
||||
IPluginHookService pluginHookService,
|
||||
EventAggregator eventAggregator) : base(logger.CreateLogger($"{payoutProcessorSettings.Processor}:{payoutProcessorSettings.StoreId}:{payoutProcessorSettings.PaymentMethod}"))
|
||||
{
|
||||
PaymentMethodId = paymentMethodId;
|
||||
_storeRepository = storeRepository;
|
||||
PayoutProcessorSettings = payoutProcessorSettings;
|
||||
PaymentMethodId = PayoutProcessorSettings.GetPaymentMethodId();
|
||||
PayoutMethodId = PayoutProcessorSettings.GetPayoutMethodId();
|
||||
_applicationDbContextFactory = applicationDbContextFactory;
|
||||
_handlers = handlers;
|
||||
_paymentHandlers = paymentHandlers;
|
||||
_pluginHookService = pluginHookService;
|
||||
_eventAggregator = eventAggregator;
|
||||
this.NoLogsOnExit = true;
|
||||
@@ -80,7 +84,7 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
|
||||
{
|
||||
if (arg.Type == PayoutEvent.PayoutEventType.Approved &&
|
||||
PayoutProcessorSettings.StoreId == arg.Payout.StoreDataId &&
|
||||
arg.Payout.GetPaymentMethodId() == PaymentMethodId &&
|
||||
arg.Payout.GetPayoutMethodId() == PayoutMethodId &&
|
||||
GetBlob(PayoutProcessorSettings).ProcessNewPayoutsInstantly)
|
||||
{
|
||||
SkipInterval();
|
||||
@@ -100,7 +104,7 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
|
||||
private async Task Act()
|
||||
{
|
||||
var store = await _storeRepository.FindStore(PayoutProcessorSettings.StoreId);
|
||||
var paymentMethod = store?.GetPaymentMethodConfig(PaymentMethodId, _handlers, true);
|
||||
var paymentMethod = store?.GetPaymentMethodConfig(PaymentMethodId, _paymentHandlers, true);
|
||||
|
||||
var blob = GetBlob(PayoutProcessorSettings);
|
||||
if (paymentMethod is not null)
|
||||
@@ -110,7 +114,7 @@ public abstract class BaseAutomatedPayoutProcessor<T> : BaseAsyncService where T
|
||||
new PullPaymentHostedService.PayoutQuery()
|
||||
{
|
||||
States = new[] { PayoutState.AwaitingPayment },
|
||||
PaymentMethods = new[] { PayoutProcessorSettings.PaymentMethod },
|
||||
PayoutMethods = new[] { PayoutProcessorSettings.PaymentMethod },
|
||||
Stores = new[] {PayoutProcessorSettings.StoreId}
|
||||
}, context, CancellationToken);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
@@ -11,8 +12,8 @@ public interface IPayoutProcessorFactory
|
||||
{
|
||||
public string Processor { get; }
|
||||
public string FriendlyName { get; }
|
||||
public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request);
|
||||
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
|
||||
public string ConfigureLink(string storeId, PayoutMethodId payoutMethodId, HttpRequest request);
|
||||
public IEnumerable<PayoutMethodId> GetSupportedPayoutMethods();
|
||||
public Task<IHostedService> ConstructProcessor(PayoutProcessorData settings);
|
||||
public Task<bool> CanRemove() => Task.FromResult(true);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@@ -35,12 +36,14 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||
private readonly IOptions<LightningNetworkOptions> _options;
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly LightningLikePayoutHandler _payoutHandler;
|
||||
public BTCPayNetwork Network => _payoutHandler.Network;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public LightningAutomatedPayoutProcessor(
|
||||
PayoutMethodId payoutMethodId,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
LightningClientFactoryService lightningClientFactoryService,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
UserService userService,
|
||||
ILoggerFactory logger, IOptions<LightningNetworkOptions> options,
|
||||
StoreRepository storeRepository, PayoutProcessorData payoutProcessorSettings,
|
||||
@@ -49,7 +52,7 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||
IPluginHookService pluginHookService,
|
||||
EventAggregator eventAggregator,
|
||||
PullPaymentHostedService pullPaymentHostedService) :
|
||||
base(logger, storeRepository, payoutProcessorSettings, applicationDbContextFactory,
|
||||
base(PaymentTypes.LN.GetPaymentMethodId(GetPayoutHandler(payoutHandlers, payoutMethodId).Network.CryptoCode), logger, storeRepository, payoutProcessorSettings, applicationDbContextFactory,
|
||||
handlers, pluginHookService, eventAggregator)
|
||||
{
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
@@ -57,16 +60,18 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||
_userService = userService;
|
||||
_options = options;
|
||||
_pullPaymentHostedService = pullPaymentHostedService;
|
||||
_payoutHandler = (LightningLikePayoutHandler)payoutHandlers.FindPayoutHandler(PaymentMethodId);
|
||||
_payoutHandler = GetPayoutHandler(payoutHandlers, payoutMethodId);
|
||||
_handlers = handlers;
|
||||
}
|
||||
private static LightningLikePayoutHandler GetPayoutHandler(PayoutMethodHandlerDictionary payoutHandlers, PayoutMethodId payoutMethodId)
|
||||
{
|
||||
return (LightningLikePayoutHandler)payoutHandlers[payoutMethodId];
|
||||
}
|
||||
|
||||
private async Task HandlePayout(PayoutData payoutData, ILightningClient lightningClient)
|
||||
{
|
||||
if (payoutData.State != PayoutState.AwaitingPayment)
|
||||
return;
|
||||
if (!_handlers.TryGetValue(PaymentMethodId, out var handler) || handler is not IHasNetwork { Network: var network })
|
||||
return;
|
||||
var res = await _pullPaymentHostedService.MarkPaid(new MarkPayoutRequest()
|
||||
{
|
||||
State = PayoutState.InProgress, PayoutId = payoutData.Id, Proof = null
|
||||
@@ -77,7 +82,7 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||
}
|
||||
|
||||
var blob = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
|
||||
var claim = await _payoutHandler.ParseClaimDestination(PaymentMethodId, blob.Destination, CancellationToken);
|
||||
var claim = await _payoutHandler.ParseClaimDestination(blob.Destination, CancellationToken);
|
||||
try
|
||||
{
|
||||
switch (claim.destination)
|
||||
@@ -85,7 +90,7 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||
case LNURLPayClaimDestinaton lnurlPayClaimDestinaton:
|
||||
var lnurlResult = await UILightningLikePayoutController.GetInvoiceFromLNURL(payoutData,
|
||||
_payoutHandler, blob,
|
||||
lnurlPayClaimDestinaton, network.NBitcoinNetwork, CancellationToken);
|
||||
lnurlPayClaimDestinaton, Network.NBitcoinNetwork, CancellationToken);
|
||||
if (lnurlResult.Item2 is null)
|
||||
{
|
||||
await TrypayBolt(lightningClient, blob, payoutData,
|
||||
@@ -113,8 +118,6 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||
|
||||
protected override async Task<bool> ProcessShouldSave(object paymentMethodConfig, List<PayoutData> payouts)
|
||||
{
|
||||
if (!_handlers.TryGetValue(PaymentMethodId, out var handler) || handler is not IHasNetwork { Network: var network })
|
||||
return false;
|
||||
var processorBlob = GetBlob(PayoutProcessorSettings);
|
||||
var lightningSupportedPaymentMethod = (LightningPaymentMethodConfig)paymentMethodConfig;
|
||||
if (lightningSupportedPaymentMethod.IsInternalNode &&
|
||||
@@ -129,7 +132,7 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||
}
|
||||
|
||||
var client =
|
||||
lightningSupportedPaymentMethod.CreateLightningClient(network, _options.Value,
|
||||
lightningSupportedPaymentMethod.CreateLightningClient(Network, _options.Value,
|
||||
_lightningClientFactoryService);
|
||||
await Task.WhenAll(payouts.Select(data => HandlePayout(data, client)));
|
||||
|
||||
@@ -143,6 +146,6 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Li
|
||||
{
|
||||
return (await UILightningLikePayoutController.TrypayBolt(lightningClient, payoutBlob, payoutData,
|
||||
bolt11PaymentRequest,
|
||||
payoutData.GetPaymentMethodId(), CancellationToken)).Result is PayResult.Ok ;
|
||||
_payoutHandler.Currency, CancellationToken)).Result is PayResult.Ok ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Payouts.LightningLike;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -13,38 +15,37 @@ namespace BTCPayServer.PayoutProcessors.Lightning;
|
||||
|
||||
public class LightningAutomatedPayoutSenderFactory : IPayoutProcessorFactory
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PayoutMethodHandlerDictionary _handlers;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly PayoutMethodId[] _supportedPayoutMethods;
|
||||
|
||||
|
||||
public LightningAutomatedPayoutSenderFactory(BTCPayNetworkProvider btcPayNetworkProvider, IServiceProvider serviceProvider, LinkGenerator linkGenerator)
|
||||
public LightningAutomatedPayoutSenderFactory(
|
||||
PayoutMethodHandlerDictionary handlers,
|
||||
IServiceProvider serviceProvider,
|
||||
LinkGenerator linkGenerator)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
_serviceProvider = serviceProvider;
|
||||
_linkGenerator = linkGenerator;
|
||||
_supportedPayoutMethods = _handlers.OfType<LightningLikePayoutHandler>().Select(n => n.PayoutMethodId).ToArray();
|
||||
}
|
||||
|
||||
public string FriendlyName { get; } = "Automated Lightning Sender";
|
||||
|
||||
public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request)
|
||||
public string ConfigureLink(string storeId, PayoutMethodId payoutMethodId, HttpRequest request)
|
||||
{
|
||||
var network = _handlers.TryGetNetwork(payoutMethodId);
|
||||
return _linkGenerator.GetUriByAction("Configure",
|
||||
"UILightningAutomatedPayoutProcessors", new
|
||||
{
|
||||
storeId,
|
||||
cryptoCode = paymentMethodId.CryptoCode
|
||||
cryptoCode = network.CryptoCode
|
||||
}, request.Scheme, request.Host, request.PathBase);
|
||||
}
|
||||
public string Processor => ProcessorName;
|
||||
public static string ProcessorName => nameof(LightningAutomatedPayoutSenderFactory);
|
||||
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||
{
|
||||
return _btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>()
|
||||
.Where(network => network.SupportLightning)
|
||||
.Select(network =>
|
||||
PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode));
|
||||
}
|
||||
public IEnumerable<PayoutMethodId> GetSupportedPayoutMethods() => _supportedPayoutMethods;
|
||||
|
||||
public Task<IHostedService> ConstructProcessor(PayoutProcessorData settings)
|
||||
{
|
||||
@@ -52,8 +53,8 @@ public class LightningAutomatedPayoutSenderFactory : IPayoutProcessorFactory
|
||||
{
|
||||
throw new NotSupportedException("This processor cannot handle the provided requirements");
|
||||
}
|
||||
|
||||
return Task.FromResult<IHostedService>(ActivatorUtilities.CreateInstance<LightningAutomatedPayoutProcessor>(_serviceProvider, settings));
|
||||
var payoutMethodId = settings.GetPayoutMethodId();
|
||||
return Task.FromResult<IHostedService>(ActivatorUtilities.CreateInstance<LightningAutomatedPayoutProcessor>(_serviceProvider, settings, payoutMethodId));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -37,8 +38,8 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Configure(string storeId, string cryptoCode)
|
||||
{
|
||||
var id = GetPaymentMethodId(cryptoCode);
|
||||
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(i => id == i))
|
||||
var id = GetPayoutMethodId(cryptoCode);
|
||||
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
@@ -53,9 +54,9 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { _lightningAutomatedPayoutSenderFactory.Processor },
|
||||
PaymentMethods = new[]
|
||||
PayoutMethodIds = new[]
|
||||
{
|
||||
PaymentTypes.LN.GetPaymentMethodId(cryptoCode)
|
||||
PayoutTypes.LN.GetPayoutMethodId(cryptoCode)
|
||||
}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
@@ -63,7 +64,7 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
|
||||
return View(new LightningTransferViewModel(activeProcessor is null ? new LightningAutomatedPayoutBlob() : LightningAutomatedPayoutProcessor.GetBlob(activeProcessor)));
|
||||
}
|
||||
|
||||
PaymentMethodId GetPaymentMethodId(string cryptoCode) => PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
PayoutMethodId GetPayoutMethodId(string cryptoCode) => PayoutTypes.LN.GetPayoutMethodId(cryptoCode);
|
||||
[HttpPost("~/stores/{storeId}/payout-processors/lightning-automated/{cryptocode}")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
@@ -71,8 +72,8 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(automatedTransferBlob);
|
||||
var id = GetPaymentMethodId(cryptoCode);
|
||||
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(i => id == i))
|
||||
var id = GetPayoutMethodId(cryptoCode);
|
||||
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
@@ -87,16 +88,16 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { _lightningAutomatedPayoutSenderFactory.Processor },
|
||||
PaymentMethods = new[]
|
||||
PayoutMethodIds = new[]
|
||||
{
|
||||
PaymentTypes.LN.GetPaymentMethodId(cryptoCode)
|
||||
PayoutTypes.LN.GetPayoutMethodId(cryptoCode)
|
||||
}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.HasTypedBlob<LightningAutomatedPayoutBlob>().SetBlob(automatedTransferBlob.ToBlob());
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = PaymentTypes.LN.GetPaymentMethodId(cryptoCode).ToString();
|
||||
activeProcessor.PaymentMethod = PayoutTypes.LN.GetPayoutMethodId(cryptoCode).ToString();
|
||||
activeProcessor.Processor = _lightningAutomatedPayoutSenderFactory.Processor;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
@@ -10,6 +11,7 @@ using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@@ -29,37 +31,47 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly BitcoinLikePayoutHandler _bitcoinLikePayoutHandler;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public BTCPayNetwork Network { get; }
|
||||
|
||||
private readonly IFeeProviderFactory _feeProviderFactory;
|
||||
|
||||
public OnChainAutomatedPayoutProcessor(
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
PayoutMethodId payoutMethodId,
|
||||
ApplicationDbContextFactory applicationDbContextFactory,
|
||||
ExplorerClientProvider explorerClientProvider,
|
||||
BTCPayWalletProvider btcPayWalletProvider,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
ILoggerFactory logger,
|
||||
BitcoinLikePayoutHandler bitcoinLikePayoutHandler,
|
||||
EventAggregator eventAggregator,
|
||||
WalletRepository walletRepository,
|
||||
StoreRepository storeRepository,
|
||||
PayoutProcessorData payoutProcesserSettings,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
IPluginHookService pluginHookService,
|
||||
IFeeProviderFactory feeProviderFactory) :
|
||||
base(logger, storeRepository, payoutProcesserSettings, applicationDbContextFactory,
|
||||
base(
|
||||
PaymentTypes.CHAIN.GetPaymentMethodId(GetPayoutHandler(payoutHandlers, payoutMethodId).Network.CryptoCode),
|
||||
logger, storeRepository, payoutProcesserSettings, applicationDbContextFactory,
|
||||
handlers, pluginHookService, eventAggregator)
|
||||
{
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_btcPayWalletProvider = btcPayWalletProvider;
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_bitcoinLikePayoutHandler = bitcoinLikePayoutHandler;
|
||||
WalletRepository = walletRepository;
|
||||
_handlers = handlers;
|
||||
_feeProviderFactory = feeProviderFactory;
|
||||
}
|
||||
{
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_btcPayWalletProvider = btcPayWalletProvider;
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_bitcoinLikePayoutHandler = GetPayoutHandler(payoutHandlers, payoutMethodId);
|
||||
Network = _bitcoinLikePayoutHandler.Network;
|
||||
WalletRepository = walletRepository;
|
||||
_feeProviderFactory = feeProviderFactory;
|
||||
}
|
||||
|
||||
public WalletRepository WalletRepository { get; }
|
||||
private static BitcoinLikePayoutHandler GetPayoutHandler(PayoutMethodHandlerDictionary payoutHandlers, PayoutMethodId payoutMethodId)
|
||||
{
|
||||
return (BitcoinLikePayoutHandler)payoutHandlers[payoutMethodId];
|
||||
}
|
||||
|
||||
public WalletRepository WalletRepository { get; }
|
||||
|
||||
protected override async Task Process(object paymentMethodConfig, List<PayoutData> payouts)
|
||||
{
|
||||
@@ -67,13 +79,12 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
{
|
||||
return;
|
||||
}
|
||||
var network = _handlers.TryGetNetwork(this.PaymentMethodId);
|
||||
if (network is null || !_explorerClientProvider.IsAvailable(network.CryptoCode))
|
||||
if (!_explorerClientProvider.IsAvailable(Network.CryptoCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(network.CryptoCode);
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(Network.CryptoCode);
|
||||
|
||||
var extKeyStr = await explorerClient.GetMetadataAsync<string>(
|
||||
config.AccountDerivation,
|
||||
@@ -83,12 +94,12 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
return;
|
||||
}
|
||||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(PaymentMethodId.CryptoCode);
|
||||
var wallet = _btcPayWalletProvider.GetWallet(Network.CryptoCode);
|
||||
|
||||
var reccoins = (await wallet.GetUnspentCoins(config.AccountDerivation)).ToArray();
|
||||
var coins = reccoins.Select(coin => coin.Coin).ToArray();
|
||||
|
||||
var accountKey = ExtKey.Parse(extKeyStr, network.NBitcoinNetwork);
|
||||
var accountKey = ExtKey.Parse(extKeyStr, Network.NBitcoinNetwork);
|
||||
var keys = reccoins.Select(coin => accountKey.Derive(coin.KeyPath).PrivateKey).ToArray();
|
||||
Transaction workingTx = null;
|
||||
decimal? failedAmount = null;
|
||||
@@ -102,7 +113,7 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
return;
|
||||
}
|
||||
|
||||
var feeRate = await this._feeProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(Math.Max(processorBlob.FeeTargetBlock, 1));
|
||||
var feeRate = await this._feeProviderFactory.CreateFeeProvider(Network).GetFeeRateAsync(Math.Max(processorBlob.FeeTargetBlock, 1));
|
||||
|
||||
var transfersProcessing = new List<KeyValuePair<PayoutData, PayoutBlob>>();
|
||||
foreach (var transferRequest in payoutToBlobs)
|
||||
@@ -114,14 +125,14 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
}
|
||||
|
||||
var claimDestination =
|
||||
await _bitcoinLikePayoutHandler.ParseClaimDestination(PaymentMethodId, blob.Destination, CancellationToken);
|
||||
await _bitcoinLikePayoutHandler.ParseClaimDestination(blob.Destination, CancellationToken);
|
||||
if (!string.IsNullOrEmpty(claimDestination.error))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var bitcoinClaimDestination = (IBitcoinLikeClaimDestination)claimDestination.destination;
|
||||
var txBuilder = network.NBitcoinNetwork.CreateTransactionBuilder()
|
||||
var txBuilder = Network.NBitcoinNetwork.CreateTransactionBuilder()
|
||||
.AddCoins(coins)
|
||||
.AddKeys(keys);
|
||||
|
||||
@@ -179,7 +190,7 @@ namespace BTCPayServer.PayoutProcessors.OnChain
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
}
|
||||
var walletId = new WalletId(PayoutProcessorSettings.StoreId, network.CryptoCode);
|
||||
var walletId = new WalletId(PayoutProcessorSettings.StoreId, Network.CryptoCode);
|
||||
foreach (var payoutData in transfersProcessing)
|
||||
{
|
||||
await WalletRepository.AddWalletTransactionAttachment(walletId,
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -15,41 +16,39 @@ namespace BTCPayServer.PayoutProcessors.OnChain;
|
||||
|
||||
public class OnChainAutomatedPayoutSenderFactory : EventHostedServiceBase, IPayoutProcessorFactory
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PayoutMethodHandlerDictionary _handlers;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly PayoutMethodId[] _supportedPayoutMethods;
|
||||
|
||||
public string FriendlyName { get; } = "Automated Bitcoin Sender";
|
||||
public OnChainAutomatedPayoutSenderFactory(EventAggregator eventAggregator,
|
||||
public OnChainAutomatedPayoutSenderFactory(
|
||||
PayoutMethodHandlerDictionary handlers,
|
||||
EventAggregator eventAggregator,
|
||||
ILogger<OnChainAutomatedPayoutSenderFactory> logger,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IServiceProvider serviceProvider, LinkGenerator linkGenerator) : base(eventAggregator, logger)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
_serviceProvider = serviceProvider;
|
||||
_linkGenerator = linkGenerator;
|
||||
_supportedPayoutMethods = _handlers.OfType<BitcoinLikePayoutHandler>().Select(c => c.PayoutMethodId).ToArray();
|
||||
}
|
||||
|
||||
public string Processor => ProcessorName;
|
||||
public static string ProcessorName => nameof(OnChainAutomatedPayoutSenderFactory);
|
||||
|
||||
public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request)
|
||||
public string ConfigureLink(string storeId, PayoutMethodId payoutMethodId, HttpRequest request)
|
||||
{
|
||||
var network = _handlers.GetNetwork(payoutMethodId);
|
||||
return _linkGenerator.GetUriByAction("Configure",
|
||||
"UIOnChainAutomatedPayoutProcessors", new
|
||||
{
|
||||
storeId,
|
||||
cryptoCode = paymentMethodId.CryptoCode
|
||||
cryptoCode = network.CryptoCode
|
||||
}, request.Scheme, request.Host, request.PathBase);
|
||||
}
|
||||
|
||||
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||
{
|
||||
return _btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>()
|
||||
.Where(network => !network.ReadonlyWallet && network.WalletSupported)
|
||||
.Select(network =>
|
||||
PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode));
|
||||
}
|
||||
public IEnumerable<PayoutMethodId> GetSupportedPayoutMethods() => _supportedPayoutMethods;
|
||||
|
||||
public Task<IHostedService> ConstructProcessor(PayoutProcessorData settings)
|
||||
{
|
||||
@@ -57,7 +56,7 @@ public class OnChainAutomatedPayoutSenderFactory : EventHostedServiceBase, IPayo
|
||||
{
|
||||
throw new NotSupportedException("This processor cannot handle the provided requirements");
|
||||
}
|
||||
|
||||
return Task.FromResult<IHostedService>(ActivatorUtilities.CreateInstance<OnChainAutomatedPayoutProcessor>(_serviceProvider, settings));
|
||||
var payoutMethodId = settings.GetPayoutMethodId();
|
||||
return Task.FromResult<IHostedService>(ActivatorUtilities.CreateInstance<OnChainAutomatedPayoutProcessor>(_serviceProvider, settings, payoutMethodId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -33,14 +34,14 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
|
||||
_onChainAutomatedPayoutSenderFactory = onChainAutomatedPayoutSenderFactory;
|
||||
_payoutProcessorService = payoutProcessorService;
|
||||
}
|
||||
PaymentMethodId GetPaymentMethodId(string cryptoCode) => PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
PayoutMethodId GetPayoutMethod(string cryptoCode) => PayoutTypes.CHAIN.GetPayoutMethodId(cryptoCode);
|
||||
[HttpGet("~/stores/{storeId}/payout-processors/onchain-automated/{cryptocode}")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Configure(string storeId, string cryptoCode)
|
||||
{
|
||||
var id = GetPaymentMethodId(cryptoCode);
|
||||
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(i => id == i))
|
||||
var id = GetPayoutMethod(cryptoCode);
|
||||
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
@@ -64,9 +65,9 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { _onChainAutomatedPayoutSenderFactory.Processor },
|
||||
PaymentMethods = new[]
|
||||
PayoutMethodIds = new[]
|
||||
{
|
||||
PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode)
|
||||
PayoutTypes.CHAIN.GetPayoutMethodId(cryptoCode)
|
||||
}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
@@ -81,8 +82,8 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(automatedTransferBlob);
|
||||
var id = GetPaymentMethodId(cryptoCode);
|
||||
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(i => id == i))
|
||||
var id = GetPayoutMethod(cryptoCode);
|
||||
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
@@ -97,16 +98,16 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = new[]
|
||||
PayoutMethodIds = new[]
|
||||
{
|
||||
PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode)
|
||||
PayoutTypes.CHAIN.GetPayoutMethodId(cryptoCode)
|
||||
}
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.HasTypedBlob<OnChainAutomatedPayoutBlob>().SetBlob(automatedTransferBlob.ToBlob());
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode).ToString();
|
||||
activeProcessor.PaymentMethod = PayoutTypes.CHAIN.GetPayoutMethodId(cryptoCode).ToString();
|
||||
activeProcessor.Processor = _onChainAutomatedPayoutSenderFactory.Processor;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
|
||||
@@ -8,6 +8,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -49,14 +50,14 @@ public class PayoutProcessorService : EventHostedServiceBase
|
||||
{
|
||||
|
||||
}
|
||||
public PayoutProcessorQuery(string storeId, PaymentMethodId paymentMethodId)
|
||||
public PayoutProcessorQuery(string storeId, PayoutMethodId payoutMethodId)
|
||||
{
|
||||
Stores = new[] { storeId };
|
||||
PaymentMethods = new[] { paymentMethodId };
|
||||
PayoutMethodIds = new[] { payoutMethodId };
|
||||
}
|
||||
public string[] Stores { get; set; }
|
||||
public string[] Processors { get; set; }
|
||||
public PaymentMethodId[] PaymentMethods { get; set; }
|
||||
public PayoutMethodId[] PayoutMethodIds { get; set; }
|
||||
}
|
||||
|
||||
public async Task<List<PayoutProcessorData>> GetProcessors(PayoutProcessorQuery query)
|
||||
@@ -72,9 +73,9 @@ public class PayoutProcessorService : EventHostedServiceBase
|
||||
{
|
||||
queryable = queryable.Where(data => query.Stores.Contains(data.StoreId));
|
||||
}
|
||||
if (query.PaymentMethods is not null)
|
||||
if (query.PayoutMethodIds is not null)
|
||||
{
|
||||
var paymentMethods = query.PaymentMethods.Select(d => d.ToString()).Distinct().ToArray();
|
||||
var paymentMethods = query.PayoutMethodIds.Select(d => d.ToString()).Distinct().ToArray();
|
||||
queryable = queryable.Where(data => paymentMethods.Contains(data.PaymentMethod));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors.Lightning;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.PayoutProcessors;
|
||||
@@ -18,8 +19,8 @@ public static class PayoutProcessorsExtensions
|
||||
serviceCollection.AddHostedService(s => s.GetRequiredService<PayoutProcessorService>());
|
||||
}
|
||||
|
||||
public static PaymentMethodId GetPaymentMethodId(this PayoutProcessorData data)
|
||||
public static PayoutMethodId GetPayoutMethodId(this PayoutProcessorData data)
|
||||
{
|
||||
return PaymentMethodId.Parse(data.PaymentMethod);
|
||||
return PayoutMethodId.Parse(data.PaymentMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payouts;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -45,9 +46,9 @@ public class UIPayoutProcessorsController : Controller
|
||||
return View(_payoutProcessorFactories.Select(factory =>
|
||||
{
|
||||
var conf = activeProcessors.FirstOrDefault(datas => datas.Key == factory.Processor)
|
||||
?.ToDictionary(data => data.GetPaymentMethodId(), data => data) ??
|
||||
new Dictionary<PaymentMethodId, PayoutProcessorData>();
|
||||
foreach (PaymentMethodId supportedPaymentMethod in factory.GetSupportedPaymentMethods())
|
||||
?.ToDictionary(data => data.GetPayoutMethodId(), data => data) ??
|
||||
new Dictionary<PayoutMethodId, PayoutProcessorData>();
|
||||
foreach (var supportedPaymentMethod in factory.GetSupportedPayoutMethods())
|
||||
{
|
||||
conf.TryAdd(supportedPaymentMethod, null);
|
||||
}
|
||||
@@ -80,7 +81,7 @@ public class UIPayoutProcessorsController : Controller
|
||||
|
||||
public class StorePayoutProcessorsView
|
||||
{
|
||||
public Dictionary<PaymentMethodId, PayoutProcessorData> Configured { get; set; }
|
||||
public Dictionary<PayoutMethodId, PayoutProcessorData> Configured { get; set; }
|
||||
public IPayoutProcessorFactory Factory { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
16
BTCPayServer/Payouts/PayoutMethodHandlerDictionary.cs
Normal file
16
BTCPayServer/Payouts/PayoutMethodHandlerDictionary.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace BTCPayServer.Payouts
|
||||
{
|
||||
public class PayoutMethodHandlerDictionary : HandlersDictionary<PayoutMethodId, IPayoutHandler>
|
||||
{
|
||||
public PayoutMethodHandlerDictionary(IEnumerable<IPayoutHandler> payoutHandlers) : base(payoutHandlers)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
85
BTCPayServer/Payouts/PayoutMethodId.cs
Normal file
85
BTCPayServer/Payouts/PayoutMethodId.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Payouts
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A value object which represent a crypto currency with his payment type (ie, onchain or offchain)
|
||||
/// </summary>
|
||||
public class PayoutMethodId
|
||||
{
|
||||
PayoutMethodId(string id)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
_Id = id;
|
||||
}
|
||||
|
||||
string _Id;
|
||||
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is PayoutMethodId id)
|
||||
return ToString().Equals(id.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||
return false;
|
||||
}
|
||||
public static bool operator ==(PayoutMethodId? a, PayoutMethodId? b)
|
||||
{
|
||||
if (a is null && b is null)
|
||||
return true;
|
||||
if (a is PayoutMethodId ai && b is PayoutMethodId bi)
|
||||
return ai.Equals(bi);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool operator !=(PayoutMethodId? a, PayoutMethodId? b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
#pragma warning disable CA1307 // Specify StringComparison
|
||||
return ToString().GetHashCode();
|
||||
#pragma warning restore CA1307 // Specify StringComparison
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _Id;
|
||||
}
|
||||
static char[] Separators = new[] { '_', '-' };
|
||||
public static PayoutMethodId? TryParse(string? str)
|
||||
{
|
||||
TryParse(str, out var r);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static bool TryParse(string? str, [MaybeNullWhen(false)] out PayoutMethodId payoutMethodId)
|
||||
{
|
||||
payoutMethodId = null;
|
||||
if (!Payments.PaymentMethodId.TryParse(str, out var result))
|
||||
return false;
|
||||
var payoutId = result.ToString();
|
||||
// -LNURL should just be -LN
|
||||
var lnUrlSuffix = $"-{Payments.PaymentTypes.LNURL.ToString()}";
|
||||
if (payoutId.EndsWith(lnUrlSuffix, StringComparison.Ordinal))
|
||||
payoutId = payoutId.Substring(payoutId.Length - lnUrlSuffix.Length) + $"-{Payments.PaymentTypes.LN}";
|
||||
|
||||
payoutMethodId = new PayoutMethodId(payoutId);
|
||||
return true;
|
||||
}
|
||||
public static PayoutMethodId Parse(string str)
|
||||
{
|
||||
if (!TryParse(str, out var result))
|
||||
throw new FormatException("Invalid PayoutMethodId");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
BTCPayServer/Payouts/PayoutTypes.cs
Normal file
18
BTCPayServer/Payouts/PayoutTypes.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Payouts
|
||||
{
|
||||
public class PayoutTypes
|
||||
{
|
||||
public static readonly PayoutType LN = new("LN");
|
||||
public static readonly PayoutType CHAIN = new("CHAIN");
|
||||
}
|
||||
public record PayoutType(string Id)
|
||||
{
|
||||
public PayoutMethodId GetPayoutMethodId(string cryptoCode) => PayoutMethodId.Parse($"{cryptoCode.ToUpperInvariant()}-{Id}");
|
||||
public override string ToString()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Payouts;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -37,15 +38,17 @@ namespace BTCPayServer.Services
|
||||
{
|
||||
serializer.Converters.Add(converter);
|
||||
}
|
||||
_Serializers.Add(network.CryptoCode, serializer);
|
||||
// TODO: Get rid of this serializer
|
||||
_Serializers.Add(PayoutTypes.CHAIN.GetPayoutMethodId(network.CryptoCode), serializer);
|
||||
_Serializers.Add(PayoutTypes.LN.GetPayoutMethodId(network.CryptoCode), serializer);
|
||||
}
|
||||
}
|
||||
|
||||
readonly Dictionary<string, JsonSerializerSettings> _Serializers = new Dictionary<string, JsonSerializerSettings>();
|
||||
public JsonSerializerSettings GetSerializer(string cryptoCode)
|
||||
readonly Dictionary<PayoutMethodId, JsonSerializerSettings> _Serializers = new Dictionary<PayoutMethodId, JsonSerializerSettings>();
|
||||
public JsonSerializerSettings GetSerializer(PayoutMethodId payoutMethodId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(cryptoCode);
|
||||
_Serializers.TryGetValue(cryptoCode, out var serializer);
|
||||
ArgumentNullException.ThrowIfNull(payoutMethodId);
|
||||
_Serializers.TryGetValue(payoutMethodId, out var serializer);
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,41 +10,10 @@ using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
public class PaymentMethodHandlerDictionary : IEnumerable<IPaymentMethodHandler>
|
||||
public class PaymentMethodHandlerDictionary : HandlersDictionary<PaymentMethodId, IPaymentMethodHandler>
|
||||
{
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentMethodHandler> _mappedHandlers =
|
||||
new Dictionary<PaymentMethodId, IPaymentMethodHandler>();
|
||||
|
||||
public PaymentMethodHandlerDictionary(IEnumerable<IPaymentMethodHandler> paymentMethodHandlers)
|
||||
public PaymentMethodHandlerDictionary(IEnumerable<IPaymentMethodHandler> paymentMethodHandlers) : base(paymentMethodHandlers)
|
||||
{
|
||||
foreach (var paymentMethodHandler in paymentMethodHandlers)
|
||||
{
|
||||
_mappedHandlers.Add(paymentMethodHandler.PaymentMethodId, paymentMethodHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(PaymentMethodId paymentMethodId, [MaybeNullWhen(false)] out IPaymentMethodHandler value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(paymentMethodId);
|
||||
return _mappedHandlers.TryGetValue(paymentMethodId, out value);
|
||||
}
|
||||
public IPaymentMethodHandler? TryGet(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(paymentMethodId);
|
||||
_mappedHandlers.TryGetValue(paymentMethodId, out var value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public IPaymentMethodHandler this[PaymentMethodId index] => _mappedHandlers[index];
|
||||
public bool Support(PaymentMethodId paymentMethod) => _mappedHandlers.ContainsKey(paymentMethod);
|
||||
public IEnumerator<IPaymentMethodHandler> GetEnumerator()
|
||||
{
|
||||
return _mappedHandlers.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public object? ParsePaymentPromptDetails(PaymentPrompt prompt)
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
||||
new
|
||||
{
|
||||
storeId = notification.StoreId,
|
||||
paymentMethodId = notification.PaymentMethod,
|
||||
payoutMethodId = notification.PaymentMethod,
|
||||
payoutState = PayoutState.AwaitingPayment
|
||||
}, _options.RootPath);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
||||
};
|
||||
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(UIStorePullPaymentsController.Payouts),
|
||||
"UIStorePullPayments",
|
||||
new { storeId = notification.StoreId, paymentMethodId = notification.PaymentMethod }, _options.RootPath);
|
||||
new { storeId = notification.StoreId, payoutMethodId = notification.PaymentMethod }, _options.RootPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Data.Payouts.LightningLike;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payouts;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Services.Reporting;
|
||||
@@ -17,12 +20,12 @@ public class PayoutsReportProvider : ReportProvider
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly DisplayFormatter _displayFormatter;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly PayoutMethodHandlerDictionary _handlers;
|
||||
|
||||
public PayoutsReportProvider(
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
DisplayFormatter displayFormatter,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
PayoutMethodHandlerDictionary handlers,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings)
|
||||
{
|
||||
_displayFormatter = displayFormatter;
|
||||
@@ -51,23 +54,27 @@ public class PayoutsReportProvider : ReportProvider
|
||||
data.Add(payout.Date);
|
||||
data.Add(payout.GetPayoutSource(_btcPayNetworkJsonSerializerSettings));
|
||||
data.Add(payout.State.ToString());
|
||||
if (PaymentMethodId.TryParse(payout.PaymentMethodId, out var pmi))
|
||||
string? payoutCurrency;
|
||||
if (PayoutMethodId.TryParse(payout.PaymentMethodId, out var pmi))
|
||||
{
|
||||
var handler = _handlers.TryGet(pmi);
|
||||
if (handler is ILightningPaymentHandler)
|
||||
if (handler is LightningLikePayoutHandler)
|
||||
data.Add("Lightning");
|
||||
else if (handler is BitcoinLikePaymentHandler)
|
||||
else if (handler is BitcoinLikePayoutHandler)
|
||||
data.Add("On-Chain");
|
||||
else
|
||||
data.Add(pmi.ToString());
|
||||
payoutCurrency = handler?.Currency;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
var ppBlob = payout.PullPaymentData?.GetBlob();
|
||||
var currency = ppBlob?.Currency ?? pmi.CryptoCode;
|
||||
data.Add(pmi.CryptoCode);
|
||||
data.Add(blob.CryptoAmount.HasValue ? _displayFormatter.ToFormattedAmount(blob.CryptoAmount.Value, pmi.CryptoCode) : null);
|
||||
var currency = ppBlob?.Currency ?? payoutCurrency;
|
||||
if (currency is null)
|
||||
continue;
|
||||
data.Add(payoutCurrency);
|
||||
data.Add(blob.CryptoAmount.HasValue && payoutCurrency is not null ? _displayFormatter.ToFormattedAmount(blob.CryptoAmount.Value, payoutCurrency) : null);
|
||||
data.Add(currency);
|
||||
data.Add(_displayFormatter.ToFormattedAmount(blob.Amount, currency));
|
||||
data.Add(blob.Destination);
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
if (Model.AvailablePaymentMethods != null && Model.AvailablePaymentMethods.Any())
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="SelectedPaymentMethod" class="form-label"></label>
|
||||
<select asp-items="Model.AvailablePaymentMethods" asp-for="SelectedPaymentMethod" class="form-select"></select>
|
||||
<span asp-validation-for="SelectedPaymentMethod" class="text-danger"></span>
|
||||
<label asp-for="SelectedPayoutMethod" class="form-label"></label>
|
||||
<select asp-items="Model.AvailablePaymentMethods" asp-for="SelectedPayoutMethod" class="form-select"></select>
|
||||
<span asp-validation-for="SelectedPayoutMethod" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<button id="ok" type="submit" class="btn btn-primary w-100">Next</button>
|
||||
@@ -36,7 +36,7 @@
|
||||
break;
|
||||
|
||||
case RefundSteps.SelectRate:
|
||||
<input type="hidden" asp-for="SelectedPaymentMethod"/>
|
||||
<input type="hidden" asp-for="SelectedPayoutMethod"/>
|
||||
<input type="hidden" asp-for="CryptoAmountThen"/>
|
||||
<input type="hidden" asp-for="FiatAmount"/>
|
||||
<input type="hidden" asp-for="OverpaidAmount"/>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
@if (await processorsView.Factory.CanRemove())
|
||||
{
|
||||
<span>-</span>
|
||||
<a asp-action="Remove" asp-route-storeId="@storeId" asp-route-id="@conf.Value.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The @Html.Encode(processorsView.Factory.FriendlyName) for @Html.Encode(conf.Key.CryptoCode) will be removed from your store.">Remove</a>
|
||||
<a asp-action="Remove" asp-route-storeId="@storeId" asp-route-id="@conf.Value.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The @Html.Encode(processorsView.Factory.FriendlyName) (@Html.Encode(conf.Key.ToString())) will be removed from your store.">Remove</a>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
|
||||
@@ -48,14 +48,18 @@
|
||||
</button>
|
||||
}
|
||||
<input class="form-control form-control-lg font-monospace" asp-for="Destination" placeholder="Enter destination to claim funds" required style="font-size:.9rem;height:42px;">
|
||||
@if (Model.PaymentMethods.Length == 1)
|
||||
@if (Model.BitcoinOnly)
|
||||
{
|
||||
<span class="input-group-text">BTC</span>
|
||||
}
|
||||
else if (Model.PayoutMethodIds.Length == 1)
|
||||
{
|
||||
<input type="hidden" asp-for="SelectedPaymentMethod">
|
||||
<span class="input-group-text">@Model.PaymentMethods.First().ToString()</span>
|
||||
<input type="hidden" asp-for="SelectedPayoutMethod">
|
||||
<span class="input-group-text">@Model.PayoutMethodIds.First().ToString()</span>
|
||||
}
|
||||
else if (!Model.BitcoinOnly)
|
||||
else
|
||||
{
|
||||
<select class="form-select w-auto" asp-for="SelectedPaymentMethod" asp-items="Model.PaymentMethods.Select(id => new SelectListItem(id.ToString(), id.ToString()))"></select>
|
||||
<select class="form-select w-auto" asp-for="SelectedPayoutMethod" asp-items="Model.PayoutMethodIds.Select(id => new SelectListItem(id.ToString(), id.ToString()))"></select>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary only-for-js" data-bs-toggle="modal" data-bs-target="#scanModal" title="Scan destination with camera" id="scandestination-button">
|
||||
<vc:icon symbol="scan-qr"/>
|
||||
|
||||
@@ -51,17 +51,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label asp-for="PaymentMethods" class="form-label"></label>
|
||||
@foreach (var item in Model.PaymentMethodItems)
|
||||
<label asp-for="PayoutMethods" class="form-label"></label>
|
||||
@foreach (var item in Model.PayoutMethodsItem)
|
||||
{
|
||||
<div class="form-check mb-2">
|
||||
<label class="form-label">
|
||||
<input name="PaymentMethods" class="form-check-input" type="checkbox" value="@item.Value" @(item.Selected ? "checked" : "")>
|
||||
<input name="PayoutMethods" class="form-check-input" type="checkbox" value="@item.Value" @(item.Selected ? "checked" : "")>
|
||||
@item.Text
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
<span asp-validation-for="PaymentMethods" class="text-danger mt-0"></span>
|
||||
<span asp-validation-for="PayoutMethods" class="text-danger mt-0"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
@using BTCPayServer.Client.Models
|
||||
@using BTCPayServer.Payments
|
||||
@using BTCPayServer.Payouts
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Client
|
||||
@using BTCPayServer.PayoutProcessors
|
||||
@model BTCPayServer.Models.WalletViewModels.PayoutsModel
|
||||
|
||||
@inject IEnumerable<IPayoutHandler> PayoutHandlers;
|
||||
@inject PayoutMethodHandlerDictionary PayoutHandlers;
|
||||
@{
|
||||
var storeId = Context.GetRouteValue("storeId") as string;
|
||||
ViewData.SetActivePage(StoreNavPages.Payouts, $"Payouts{(string.IsNullOrEmpty(Model.PullPaymentName) ? string.Empty : " for pull payment " + Model.PullPaymentName)}", Context.GetStoreData().Id);
|
||||
Model.PaginationQuery ??= new Dictionary<string, object>();
|
||||
Model.PaginationQuery.Add("pullPaymentId", Model.PullPaymentId);
|
||||
Model.PaginationQuery.Add("paymentMethodId", Model.PaymentMethodId);
|
||||
Model.PaginationQuery.Add("payoutMethodId", Model.PayoutMethodId);
|
||||
Model.PaginationQuery.Add("payoutState", Model.PayoutState);
|
||||
var stateActions = new List<(string Action, string Text)>();
|
||||
if (PaymentMethodId.TryParse(Model.PaymentMethodId, out var paymentMethodId))
|
||||
if (PayoutMethodId.TryParse(Model.PayoutMethodId, out var payoutMethodId))
|
||||
{
|
||||
var payoutHandler = PayoutHandlers.FindPayoutHandler(paymentMethodId);
|
||||
var payoutHandler = PayoutHandlers.TryGet(payoutMethodId);
|
||||
if (payoutHandler is null)
|
||||
return;
|
||||
stateActions.AddRange(payoutHandler.GetPayoutSpecificActions().Where(pair => pair.Key == Model.PayoutState).SelectMany(pair => pair.Value));
|
||||
@@ -96,22 +97,22 @@
|
||||
}
|
||||
|
||||
<form method="post" id="Payouts">
|
||||
<input type="hidden" asp-for="PaymentMethodId" />
|
||||
<input type="hidden" asp-for="PayoutMethodId" />
|
||||
<input type="hidden" asp-for="PayoutState" />
|
||||
<div class="d-flex justify-content-between mb-4">
|
||||
<ul class="nav mb-1 gap-2">
|
||||
@foreach (var state in Model.PaymentMethods)
|
||||
@foreach (var state in Model.PayoutMethods)
|
||||
{
|
||||
<li class="nav-item py-0">
|
||||
<a asp-action="Payouts" asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-payoutState="@Model.PayoutState"
|
||||
asp-route-paymentMethodId="@state.ToString()"
|
||||
asp-route-payoutMethodId="@state.ToString()"
|
||||
asp-route-pullPaymentId="@Model.PullPaymentId"
|
||||
class="btcpay-pill position-relative me-0 @(state.ToString() == Model.PaymentMethodId ? "active" : "")"
|
||||
class="btcpay-pill position-relative me-0 @(state.ToString() == Model.PayoutMethodId ? "active" : "")"
|
||||
id="@state.ToString()-view"
|
||||
role="tab">
|
||||
@state.ToString()
|
||||
@if (Model.PaymentMethodCount.TryGetValue(state.ToString(), out var count) && count > 0)
|
||||
@if (Model.PayoutMethodCount.TryGetValue(state.ToString(), out var count) && count > 0)
|
||||
{
|
||||
<span class="badge rounded-pill fw-semibold pe-0">@count</span>
|
||||
}
|
||||
@@ -129,7 +130,7 @@
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-payoutState="@state.Key"
|
||||
asp-route-pullPaymentId="@Model.PullPaymentId"
|
||||
asp-route-paymentMethodId="@Model.PaymentMethodId"
|
||||
asp-route-payoutMethodId="@Model.PayoutMethodId"
|
||||
class="nav-link @(state.Key == Model.PayoutState ? "active" : "")" role="tab">
|
||||
@state.Key.GetStateString()
|
||||
@if (state.Value > 0)
|
||||
|
||||
Reference in New Issue
Block a user