BTCPay Abstractions: Move PaymentMethod specific logic to their handlers (#850)

This commit is contained in:
Andrew Camilleri
2019-05-29 14:33:31 +00:00
committed by Nicolas Dorier
parent d3e3c31b0c
commit 81dae7d350
26 changed files with 633 additions and 357 deletions

View File

@@ -275,6 +275,8 @@ namespace BTCPayServer.Tests
return _Host.Services.GetRequiredService<T>();
}
public IServiceProvider ServiceProvider => _Host.Services;
public T GetController<T>(string userId = null, string storeId = null, Claim[] additionalClaims = null) where T : Controller
{
var context = new DefaultHttpContext();

View File

@@ -60,6 +60,7 @@ using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Services.U2F.Models;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Tests
@@ -98,41 +99,55 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void CanCalculateCryptoDue2()
{
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
#pragma warning disable CS0618
InvoiceEntity invoiceEntity = new InvoiceEntity();
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null),
});
InvoiceEntity invoiceEntity = new InvoiceEntity() { PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary};
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
invoiceEntity.ProductInformation = new ProductInformation() {Price = 100};
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
paymentMethods.Add(new PaymentMethod()
paymentMethods.Add(new PaymentMethod() {CryptoCode = "BTC", Rate = 10513.44m,}.SetPaymentMethodDetails(
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
{
CryptoCode = "BTC",
Rate = 10513.44m,
}.SetPaymentMethodDetails(new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
{
NextNetworkFee = Money.Coins(0.00000100m),
DepositAddress = dummy
NextNetworkFee = Money.Coins(0.00000100m), DepositAddress = dummy
}));
paymentMethods.Add(new PaymentMethod()
paymentMethods.Add(new PaymentMethod() {CryptoCode = "LTC", Rate = 216.79m}.SetPaymentMethodDetails(
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
{
CryptoCode = "LTC",
Rate = 216.79m
}.SetPaymentMethodDetails(new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
{
NextNetworkFee = Money.Coins(0.00010000m),
DepositAddress = dummy
NextNetworkFee = Money.Coins(0.00010000m), DepositAddress = dummy
}));
invoiceEntity.SetPaymentMethods(paymentMethods);
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
var accounting = btc.Calculate();
invoiceEntity.Payments.Add(new PaymentEntity() { Accounted = true, CryptoCode = "BTC", NetworkFee = 0.00000100m }.SetCryptoPaymentData(new BitcoinLikePaymentData()
invoiceEntity.Payments.Add(
new PaymentEntity()
{
Accounted = true,
CryptoCode = "BTC",
NetworkFee = 0.00000100m,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
}
.SetCryptoPaymentData(new BitcoinLikePaymentData()
{
Output = new TxOut() {Value = Money.Coins(0.00151263m)}
}));
accounting = btc.Calculate();
invoiceEntity.Payments.Add(new PaymentEntity() { Accounted = true, CryptoCode = "BTC", NetworkFee = 0.00000100m }.SetCryptoPaymentData(new BitcoinLikePaymentData()
invoiceEntity.Payments.Add(
new PaymentEntity()
{
Accounted = true,
CryptoCode = "BTC",
NetworkFee = 0.00000100m,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
}
.SetCryptoPaymentData(new BitcoinLikePaymentData()
{
Output = new TxOut() {Value = accounting.Due}
}));
@@ -195,10 +210,19 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void CanCalculateCryptoDue()
{
var entity = new InvoiceEntity();
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null),
});
var entity = new InvoiceEntity() {PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary};
#pragma warning disable CS0618
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
entity.SetPaymentMethod(new PaymentMethod() { CryptoCode = "BTC", Rate = 5000, NextNetworkFee = Money.Coins(0.1m) });
entity.SetPaymentMethod(new PaymentMethod()
{
CryptoCode = "BTC", Rate = 5000, NextNetworkFee = Money.Coins(0.1m)
});
entity.ProductInformation = new ProductInformation() {Price = 5000};
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
@@ -206,46 +230,61 @@ namespace BTCPayServer.Tests
Assert.Equal(Money.Coins(1.1m), accounting.Due);
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()), Accounted = true, NetworkFee = 0.1m });
entity.Payments.Add(new PaymentEntity()
{
Output = new TxOut(Money.Coins(0.5m), new Key()),
Accounted = true,
NetworkFee = 0.1m,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
});
accounting = paymentMethod.Calculate();
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
Assert.Equal(Money.Coins(0.7m), accounting.Due);
Assert.Equal(Money.Coins(1.2m), accounting.TotalDue);
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true, NetworkFee = 0.1m });
entity.Payments.Add(new PaymentEntity()
{
Output = new TxOut(Money.Coins(0.2m), new Key()),
Accounted = true,
NetworkFee = 0.1m,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
});
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(0.6m), accounting.Due);
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()), Accounted = true, NetworkFee = 0.1m });
entity.Payments.Add(new PaymentEntity()
{
Output = new TxOut(Money.Coins(0.6m), new Key()),
Accounted = true,
NetworkFee = 0.1m,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
});
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Zero, accounting.Due);
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
entity.Payments.Add(new PaymentEntity()
{
Output = new TxOut(Money.Coins(0.2m), new Key()),
Accounted = true,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
});
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Zero, accounting.Due);
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
entity = new InvoiceEntity();
entity = new InvoiceEntity() {PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary};
entity.ProductInformation = new ProductInformation() {Price = 5000};
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
paymentMethods.Add(new PaymentMethod()
{
CryptoCode = "BTC",
Rate = 1000,
NextNetworkFee = Money.Coins(0.1m)
});
paymentMethods.Add(new PaymentMethod()
{
CryptoCode = "LTC",
Rate = 500,
NextNetworkFee = Money.Coins(0.01m)
});
paymentMethods.Add(
new PaymentMethod() {CryptoCode = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m)});
paymentMethods.Add(
new PaymentMethod() {CryptoCode = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m)});
entity.SetPaymentMethods(paymentMethods);
entity.Payments = new List<PaymentEntity>();
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
@@ -254,9 +293,17 @@ namespace BTCPayServer.Tests
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike), null);
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true, NetworkFee = 0.1m });
entity.Payments.Add(new PaymentEntity()
{
CryptoCode = "BTC",
Output = new TxOut(Money.Coins(1.0m), new Key()),
Accounted = true,
NetworkFee = 0.1m,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
});
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
accounting = paymentMethod.Calculate();
@@ -273,8 +320,14 @@ namespace BTCPayServer.Tests
Assert.Equal(Money.Coins(2.0m), accounting.Paid);
Assert.Equal(Money.Coins(10.01m + 0.1m * 2), accounting.TotalDue);
entity.Payments.Add(new PaymentEntity() { CryptoCode = "LTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true, NetworkFee = 0.01m });
entity.Payments.Add(new PaymentEntity()
{
CryptoCode = "LTC",
Output = new TxOut(Money.Coins(1.0m), new Key()),
Accounted = true,
NetworkFee = 0.01m,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
});
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
accounting = paymentMethod.Calculate();
@@ -293,7 +346,14 @@ namespace BTCPayServer.Tests
Assert.Equal(2, accounting.TxRequired);
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2);
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(remaining, new Key()), Accounted = true, NetworkFee = 0.1m });
entity.Payments.Add(new PaymentEntity()
{
CryptoCode = "BTC",
Output = new TxOut(remaining, new Key()),
Accounted = true,
NetworkFee = 0.1m,
PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary
});
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
accounting = paymentMethod.Calculate();
@@ -310,7 +370,8 @@ namespace BTCPayServer.Tests
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
Assert.Equal(Money.Coins(3.0m) + remaining * 2, accounting.Paid);
// Paying 2 BTC fee, LTC fee removed because fully paid
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */), accounting.TotalDue);
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */),
accounting.TotalDue);
Assert.Equal(1, accounting.TxRequired);
Assert.Equal(accounting.Paid, accounting.TotalDue);
#pragma warning restore CS0618
@@ -353,13 +414,22 @@ namespace BTCPayServer.Tests
}
[Fact]
[Trait("Integration", "Integration")]
[Trait("Fast", "Fast")]
public void CanAcceptInvoiceWithTolerance()
{
var entity = new InvoiceEntity();
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null),
});
var entity = new InvoiceEntity() {PaymentMethodHandlerDictionary = paymentMethodHandlerDictionary};
#pragma warning disable CS0618
entity.Payments = new List<PaymentEntity>();
entity.SetPaymentMethod(new PaymentMethod() { CryptoCode = "BTC", Rate = 5000, NextNetworkFee = Money.Coins(0.1m) });
entity.SetPaymentMethod(new PaymentMethod()
{
CryptoCode = "BTC", Rate = 5000, NextNetworkFee = Money.Coins(0.1m)
});
entity.ProductInformation = new ProductInformation() {Price = 5000};
entity.PaymentTolerance = 0;

View File

@@ -7,9 +7,7 @@ using BTCPayServer.Models;
using BTCPayServer.Security;
using BTCPayServer.Services.Invoices;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using NBitpayClient;
namespace BTCPayServer.Controllers
{
@@ -19,15 +17,12 @@ namespace BTCPayServer.Controllers
{
private InvoiceController _InvoiceController;
private InvoiceRepository _InvoiceRepository;
private BTCPayNetworkProvider _NetworkProvider;
public InvoiceControllerAPI(InvoiceController invoiceController,
InvoiceRepository invoceRepository,
BTCPayNetworkProvider networkProvider)
InvoiceRepository invoceRepository)
{
this._InvoiceController = invoiceController;
this._InvoiceRepository = invoceRepository;
this._NetworkProvider = networkProvider;
_InvoiceController = invoiceController;
_InvoiceRepository = invoceRepository;
}
[HttpPost]
@@ -51,8 +46,7 @@ namespace BTCPayServer.Controllers
})).FirstOrDefault();
if (invoice == null)
throw new BitpayHttpException(404, "Object not found");
var resp = invoice.EntityToDTO();
return new DataWrapper<InvoiceResponse>(resp);
return new DataWrapper<InvoiceResponse>(invoice.EntityToDTO());
}
[HttpGet]
[Route("invoices")]

View File

@@ -19,10 +19,8 @@ using BTCPayServer.Security;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Invoices.Export;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore.Internal;
using NBitcoin;
using NBitpayClient;
using NBXplorer;
@@ -77,10 +75,13 @@ namespace BTCPayServer.Controllers
StatusMessage = StatusMessage
};
model.Addresses = invoice.HistoricalAddresses.Select(h => new InvoiceDetailsModel.AddressModel
model.Addresses = invoice.HistoricalAddresses.Select(h =>
new InvoiceDetailsModel.AddressModel
{
Destination = h.GetAddress(),
PaymentMethod = ToString(h.GetPaymentMethodId()),
PaymentMethod =
invoice.PaymentMethodHandlerDictionary[h.GetPaymentMethodId()]
.ToPrettyString(h.GetPaymentMethodId()),
Current = !h.UnAssigned.HasValue
}).ToArray();
@@ -101,11 +102,12 @@ namespace BTCPayServer.Controllers
var accounting = data.Calculate();
var paymentMethodId = data.GetId();
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
cryptoPayment.PaymentMethod = ToString(paymentMethodId);
cryptoPayment.PaymentMethod = invoice.PaymentMethodHandlerDictionary[paymentMethodId]
.ToPrettyString(data.GetId());
cryptoPayment.Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
cryptoPayment.Paid = _CurrencyNameTable.DisplayFormatCurrency(accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
cryptoPayment.Overpaid = _CurrencyNameTable.DisplayFormatCurrency(accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
//TODO: abstract
var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
if (onchainMethod != null)
{
@@ -160,21 +162,6 @@ namespace BTCPayServer.Controllers
return model;
}
private string ToString(PaymentMethodId paymentMethodId)
{
var type = paymentMethodId.PaymentType.ToString();
switch (paymentMethodId.PaymentType)
{
case PaymentTypes.BTCLike:
type = "On-Chain";
break;
case PaymentTypes.LightningLike:
type = "Off-Chain";
break;
}
return $"{paymentMethodId.CryptoCode} ({type})";
}
[HttpGet]
[Route("i/{invoiceId}")]
[Route("i/{invoiceId}/{paymentMethodId}")]
@@ -229,7 +216,7 @@ namespace BTCPayServer.Controllers
return View(model);
}
//TODO: abstract
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId)
{
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
@@ -245,6 +232,7 @@ namespace BTCPayServer.Controllers
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
if (network == null && isDefaultPaymentId)
{
//TODO: need to look into a better way for this as it does not scale
network = _NetworkProvider.GetAll().OfType<BTCPayNetwork>().FirstOrDefault();
paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
}
@@ -291,10 +279,6 @@ namespace BTCPayServer.Controllers
{
CryptoCode = network.CryptoCode,
RootPath = this.Request.PathBase.Value.WithTrailingSlash(),
PaymentMethodId = paymentMethodId.ToString(),
PaymentMethodName = GetDisplayName(paymentMethodId, network),
CryptoImage = GetImage(paymentMethodId, network),
IsLightning = paymentMethodId.PaymentType == PaymentTypes.LightningLike,
OrderId = invoice.OrderId,
InvoiceId = invoice.Id,
DefaultLang = storeBlob.DefaultLang ?? "en",
@@ -316,13 +300,7 @@ namespace BTCPayServer.Controllers
MerchantRefLink = invoice.RedirectURL ?? "/",
RedirectAutomatically = invoice.RedirectAutomatically,
StoreName = store.StoreName,
InvoiceBitcoinUrl = paymentMethodId.PaymentType == PaymentTypes.BTCLike ? cryptoInfo.PaymentUrls.BIP21 :
paymentMethodId.PaymentType == PaymentTypes.LightningLike ? cryptoInfo.PaymentUrls.BOLT11 :
throw new NotSupportedException(),
PeerInfo = (paymentMethodDetails as LightningLikePaymentMethodDetails)?.NodeInfo,
InvoiceBitcoinUrlQR = paymentMethodId.PaymentType == PaymentTypes.BTCLike ? cryptoInfo.PaymentUrls.BIP21 :
paymentMethodId.PaymentType == PaymentTypes.LightningLike ? cryptoInfo.PaymentUrls.BOLT11.ToUpperInvariant() :
throw new NotSupportedException(),
TxCount = accounting.TxRequired,
BtcPaid = accounting.Paid.ToString(),
#pragma warning disable CS0618 // Type or member is obsolete
@@ -340,37 +318,38 @@ namespace BTCPayServer.Controllers
StoreId = store.Id,
AvailableCryptos = invoice.GetPaymentMethods()
.Where(i => i.Network != null)
.Select(kv => new PaymentModel.AvailableCrypto()
.Select(kv =>
{
var availableCryptoPaymentMethodId = kv.GetId();
var availableCryptoHandler =
invoice.PaymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
return new PaymentModel.AvailableCrypto()
{
PaymentMethodId = kv.GetId().ToString(),
CryptoCode = kv.GetId().CryptoCode,
PaymentMethodName = GetDisplayName(kv.GetId(), kv.Network),
IsLightning = kv.GetId().PaymentType == PaymentTypes.LightningLike,
CryptoImage = GetImage(kv.GetId(), kv.Network),
Link = Url.Action(nameof(Checkout), new { invoiceId = invoiceId, paymentMethodId = kv.GetId().ToString() })
PaymentMethodName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId),
IsLightning =
kv.GetId().PaymentType == PaymentTypes.LightningLike,
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
Link = Url.Action(nameof(Checkout),
new
{
invoiceId = invoiceId,
paymentMethodId = kv.GetId().ToString()
})
};
}).Where(c => c.CryptoImage != "/")
.OrderByDescending(a => a.CryptoCode == "BTC").ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
.ToList()
};
invoice.PaymentMethodHandlerDictionary[paymentMethod.GetId()].PreparePaymentModel(model, dto);
model.PaymentMethodId = paymentMethodId.ToString();
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
model.TimeLeft = expiration.PrettyPrint();
return model;
}
private string GetDisplayName(PaymentMethodId paymentMethodId, BTCPayNetworkBase network)
{
return paymentMethodId.PaymentType == PaymentTypes.BTCLike ?
network.DisplayName : network.DisplayName + " (Lightning)";
}
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetworkBase network)
{
//the direct casting ((BTCPayNetwork)network) ) for ln image is only temp..this method is offloaded to payment handlers in other pull requests
return paymentMethodId.PaymentType == PaymentTypes.BTCLike ?
this.Request.GetRelativePathOrAbsolute(network.CryptoImagePath) : this.Request.GetRelativePathOrAbsolute(((BTCPayNetwork)network).LightningImagePath);
}
private string OrderAmountFromInvoice(string cryptoCode, ProductInformation productInformation)
{
// if invoice source currency is the same as currently display currency, no need for "order amount from invoice"
@@ -548,20 +527,13 @@ namespace BTCPayServer.Controllers
private SelectList GetPaymentMethodsSelectList()
{
return new SelectList(
_NetworkProvider.GetAll()
.SelectMany(network => new[]
{
new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike),
new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike)
}).Select(id =>
{
var handler = _paymentMethodHandlers.GetCorrectHandler(id);
return new SelectListItem(handler.ToPrettyString(id), id.ToString());
}),
return new SelectList(_paymentMethodHandlerDictionary.Distinct().SelectMany(handler =>
handler.GetSupportedPaymentMethods()
.Select(id => new SelectListItem(handler.ToPrettyString(id), id.ToString()))),
nameof(SelectListItem.Value),
nameof(SelectListItem.Text));
}
[HttpGet]
[Route("invoices/create")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]

View File

@@ -36,8 +36,7 @@ namespace BTCPayServer.Controllers
private CurrencyNameTable _CurrencyNameTable;
EventAggregator _EventAggregator;
BTCPayNetworkProvider _NetworkProvider;
private readonly IEnumerable<IPaymentMethodHandler> _paymentMethodHandlers;
private readonly BTCPayWalletProvider _WalletProvider;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
IServiceProvider _ServiceProvider;
public InvoiceController(
IServiceProvider serviceProvider,
@@ -47,10 +46,9 @@ namespace BTCPayServer.Controllers
RateFetcher rateProvider,
StoreRepository storeRepository,
EventAggregator eventAggregator,
BTCPayWalletProvider walletProvider,
ContentSecurityPolicies csp,
BTCPayNetworkProvider networkProvider,
IEnumerable<IPaymentMethodHandler> paymentMethodHandlers)
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
{
_ServiceProvider = serviceProvider;
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
@@ -60,8 +58,7 @@ namespace BTCPayServer.Controllers
_UserManager = userManager;
_EventAggregator = eventAggregator;
_NetworkProvider = networkProvider;
_paymentMethodHandlers = paymentMethodHandlers;
_WalletProvider = walletProvider;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_CSP = csp;
}
@@ -72,12 +69,7 @@ namespace BTCPayServer.Controllers
throw new UnauthorizedAccessException();
InvoiceLogs logs = new InvoiceLogs();
logs.Write("Creation of invoice starting");
var entity = new InvoiceEntity
{
Version = InvoiceEntity.Lastest_Version,
InvoiceTime = DateTimeOffset.UtcNow,
Networks = _NetworkProvider
};
var entity = _InvoiceRepository.CreateNewInvoice();
var getAppsTaggingStore = _InvoiceRepository.GetAppsTaggingStore(store.Id);
var storeBlob = store.GetStoreBlob();
@@ -168,7 +160,7 @@ namespace BTCPayServer.Controllers
var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
.Where(s => !excludeFilter.Match(s.PaymentId))
.Select(c =>
(Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler<>).MakeGenericType(c.GetType())),
(Handler: _paymentMethodHandlerDictionary[c.PaymentId],
SupportedPaymentMethod: c,
Network: _NetworkProvider.GetNetwork<BTCPayNetworkBase>(c.PaymentId.CryptoCode)))
.Where(c => c.Network != null)
@@ -272,38 +264,15 @@ namespace BTCPayServer.Controllers
paymentMethod.SetPaymentMethodDetails(paymentDetails);
}
Func<Money, Money, bool> compare = null;
CurrencyValue limitValue = null;
string errorMessage = null;
if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike &&
storeBlob.LightningMaxValue != null)
{
compare = (a, b) => a > b;
limitValue = storeBlob.LightningMaxValue;
errorMessage = "The amount of the invoice is too high to be paid with lightning";
}
else if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.BTCLike &&
storeBlob.OnChainMinValue != null)
{
compare = (a, b) => a < b;
limitValue = storeBlob.OnChainMinValue;
errorMessage = "The amount of the invoice is too low to be paid on chain";
}
if (compare != null)
{
var limitValueRate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, limitValue.Currency)];
if (limitValueRate.BidAsk != null)
{
var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.BidAsk.Bid);
if (compare(paymentMethod.Calculate().Due, limitValueCrypto))
var errorMessage = await
handler
.IsPaymentMethodAllowedBasedOnInvoiceAmount(storeBlob, fetchingByCurrencyPair,
paymentMethod.Calculate().Due, supportedPaymentMethod.PaymentId);
if (errorMessage != null)
{
logs.Write($"{logPrefix} {errorMessage}");
return null;
}
}
}
///////////////
#pragma warning disable CS0618

View File

@@ -17,6 +17,7 @@ using BTCPayServer.Payments.Lightning;
using BTCPayServer.Rating;
using BTCPayServer.Security;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
@@ -56,7 +57,8 @@ namespace BTCPayServer.Controllers
LanguageService langService,
ChangellyClientProvider changellyClientProvider,
IOptions<MvcJsonOptions> mvcJsonOptions,
IHostingEnvironment env, IHttpClientFactory httpClientFactory)
IHostingEnvironment env, IHttpClientFactory httpClientFactory,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
{
_RateFactory = rateFactory;
_Repo = repo;
@@ -69,6 +71,7 @@ namespace BTCPayServer.Controllers
_WalletProvider = walletProvider;
_Env = env;
_httpClientFactory = httpClientFactory;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_NetworkProvider = networkProvider;
_ExplorerProvider = explorerProvider;
_FeeRateProvider = feeRateProvider;
@@ -91,6 +94,7 @@ namespace BTCPayServer.Controllers
private readonly ChangellyClientProvider _changellyClientProvider;
IHostingEnvironment _Env;
private IHttpClientFactory _httpClientFactory;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
[TempData]
public string StatusMessage
@@ -362,7 +366,7 @@ namespace BTCPayServer.Controllers
void SetCryptoCurrencies(CheckoutExperienceViewModel vm, Data.StoreData storeData)
{
var choices = storeData.GetEnabledPaymentIds(_NetworkProvider)
.Select(o => new CheckoutExperienceViewModel.Format() { Name = GetDisplayName(o), Value = o.ToString(), PaymentId = o }).ToArray();
.Select(o => new CheckoutExperienceViewModel.Format() { Name = _paymentMethodHandlerDictionary[o].ToPrettyString(o), Value = o.ToString(), PaymentId = o }).ToArray();
var defaultPaymentId = storeData.GetDefaultPaymentId(_NetworkProvider);
var chosen = choices.FirstOrDefault(c => c.PaymentId == defaultPaymentId);
@@ -370,13 +374,6 @@ namespace BTCPayServer.Controllers
vm.DefaultPaymentMethod = chosen?.Value;
}
private string GetDisplayName(PaymentMethodId paymentMethodId)
{
var display = _NetworkProvider.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode)?.DisplayName ?? paymentMethodId.CryptoCode;
return paymentMethodId.PaymentType == PaymentTypes.BTCLike ?
display : $"{display} (Lightning)";
}
[HttpPost]
[Route("{storeId}/checkout")]
public async Task<IActionResult> CheckoutExperience(CheckoutExperienceViewModel model)
@@ -472,35 +469,37 @@ namespace BTCPayServer.Controllers
.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<DerivationSchemeSettings>()
.ToDictionary(c => c.Network.CryptoCode);
foreach (var network in _NetworkProvider.GetAll())
{
var strategy = derivationByCryptoCode.TryGet(network.CryptoCode);
vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme()
{
Crypto = network.CryptoCode,
Value = strategy?.ToPrettyString() ?? string.Empty,
WalletId = new WalletId(store.Id, network.CryptoCode),
Enabled = !excludeFilters.Match(new Payments.PaymentMethodId(network.CryptoCode, Payments.PaymentTypes.BTCLike))
});
}
var lightningByCryptoCode = store
.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<Payments.Lightning.LightningSupportedPaymentMethod>()
.OfType<LightningSupportedPaymentMethod>()
.ToDictionary(c => c.CryptoCode);
foreach (var network in _NetworkProvider.GetAll())
foreach (var paymentMethodId in _paymentMethodHandlerDictionary.Distinct().SelectMany(handler => handler.GetSupportedPaymentMethods()))
{
var lightning = lightningByCryptoCode.TryGet(network.CryptoCode);
var paymentId = new Payments.PaymentMethodId(network.CryptoCode, Payments.PaymentTypes.LightningLike);
switch (paymentMethodId.PaymentType)
{
case PaymentTypes.BTCLike:
var strategy = derivationByCryptoCode.TryGet(paymentMethodId.CryptoCode);
vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme()
{
Crypto = paymentMethodId.CryptoCode,
Value = strategy?.ToPrettyString() ?? string.Empty,
WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode),
Enabled = !excludeFilters.Match(paymentMethodId)
});
break;
case PaymentTypes.LightningLike:
var lightning = lightningByCryptoCode.TryGet(paymentMethodId.CryptoCode);
vm.LightningNodes.Add(new StoreViewModel.LightningNode()
{
CryptoCode = network.CryptoCode,
CryptoCode = paymentMethodId.CryptoCode,
Address = lightning?.GetLightningUrl()?.BaseUri.AbsoluteUri ?? string.Empty,
Enabled = !excludeFilters.Match(paymentId)
Enabled = !excludeFilters.Match(paymentMethodId)
});
break;
}
}
var changellyEnabled = storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled;
vm.ThirdPartyPaymentMethods.Add(new StoreViewModel.ThirdPartyPaymentMethod()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

View File

@@ -39,11 +39,6 @@ namespace BTCPayServer
{
public static class Extensions
{
public static IPaymentMethodHandler GetCorrectHandler(
this IEnumerable<IPaymentMethodHandler> paymentMethodHandlers, PaymentMethodId paymentMethodId)
{
return paymentMethodHandlers.First(handler => handler.CanHandle(paymentMethodId));
}
public static string PrettyPrint(this TimeSpan expiration)
{
StringBuilder builder = new StringBuilder();

View File

@@ -42,7 +42,6 @@ namespace BTCPayServer.HostedServices
IBackgroundJobClient _JobClient;
EventAggregator _EventAggregator;
InvoiceRepository _InvoiceRepository;
BTCPayNetworkProvider _NetworkProvider;
private readonly EmailSenderFactory _EmailSenderFactory;
public InvoiceNotificationManager(
@@ -51,14 +50,12 @@ namespace BTCPayServer.HostedServices
EventAggregator eventAggregator,
InvoiceRepository invoiceRepository,
BTCPayNetworkProvider networkProvider,
ILogger<InvoiceNotificationManager> logger,
EmailSenderFactory emailSenderFactory)
{
_Client = httpClientFactory.CreateClient();
_JobClient = jobClient;
_EventAggregator = eventAggregator;
_InvoiceRepository = invoiceRepository;
_NetworkProvider = networkProvider;
_EmailSenderFactory = emailSenderFactory;
}

View File

@@ -1,7 +1,5 @@
using NBXplorer;
using Microsoft.Extensions.Logging;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -11,12 +9,8 @@ using BTCPayServer.Logging;
using System.Threading;
using Microsoft.Extensions.Hosting;
using System.Collections.Concurrent;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Controllers;
using BTCPayServer.Events;
using Microsoft.AspNetCore.Hosting;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services;
namespace BTCPayServer.HostedServices
{

View File

@@ -46,7 +46,7 @@ using BTCPayServer.Services.Apps;
using OpenIddict.EntityFrameworkCore.Models;
using BTCPayServer.Services.U2F;
using BundlerMinifier.TagHelpers;
using System.Collections.Generic;
namespace BTCPayServer.Hosting
{
public static class BTCPayServerServices
@@ -71,10 +71,12 @@ namespace BTCPayServer.Hosting
{
var opts = o.GetRequiredService<BTCPayServerOptions>();
var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
var paymentMethodHandlerDictionary = o.GetService<PaymentMethodHandlerDictionary>();
var dbpath = Path.Combine(opts.DataDir, "InvoiceDB");
if (!Directory.Exists(dbpath))
Directory.CreateDirectory(dbpath);
return new InvoiceRepository(dbContext, dbpath, o.GetRequiredService<BTCPayNetworkProvider>());
return new InvoiceRepository(dbContext, dbpath, o.GetRequiredService<BTCPayNetworkProvider>(),
paymentMethodHandlerDictionary);
});
services.AddSingleton<BTCPayServerEnvironment>();
services.TryAddSingleton<TokenRepository>();
@@ -179,14 +181,16 @@ namespace BTCPayServer.Hosting
services.AddSingleton<BitcoinLikePaymentHandler>();
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<BitcoinLikePaymentHandler>());
services.AddSingleton<IPaymentMethodHandler<DerivationSchemeSettings>>(provider => provider.GetService<BitcoinLikePaymentHandler>());
services.AddSingleton<IPaymentMethodHandler<DerivationSchemeSettings, BTCPayNetwork>>(provider => provider.GetService<BitcoinLikePaymentHandler>());
services.AddSingleton<IHostedService, NBXplorerListener>();
services.AddSingleton<LightningLikePaymentHandler>();
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LightningLikePaymentHandler>());
services.AddSingleton<IPaymentMethodHandler<LightningSupportedPaymentMethod>>(provider => provider.GetService<LightningLikePaymentHandler>());
services.AddSingleton<IPaymentMethodHandler<LightningSupportedPaymentMethod, BTCPayNetwork>>(provider => provider.GetService<LightningLikePaymentHandler>());
services.AddSingleton<IHostedService, LightningListener>();
services.AddSingleton<PaymentMethodHandlerDictionary>();
services.AddSingleton<ChangellyClientProvider>();
services.AddSingleton<IHostedService, NBXplorerWaiters>();

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using System.Threading;

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Models.PaymentRequestViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;

View File

@@ -1,29 +1,34 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Rating;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Bitcoin
{
public class BitcoinLikePaymentHandler : PaymentMethodHandlerBase<DerivationSchemeSettings>
public class BitcoinLikePaymentHandler : PaymentMethodHandlerBase<DerivationSchemeSettings, BTCPayNetwork>
{
ExplorerClientProvider _ExplorerProvider;
private readonly BTCPayNetworkProvider _networkProvider;
private IFeeProviderFactory _FeeRateProviderFactory;
private Services.Wallets.BTCPayWalletProvider _WalletProvider;
public BitcoinLikePaymentHandler(ExplorerClientProvider provider,
BTCPayNetworkProvider networkProvider,
IFeeProviderFactory feeRateProviderFactory,
Services.Wallets.BTCPayWalletProvider walletProvider)
{
if (provider == null)
throw new ArgumentNullException(nameof(provider));
_ExplorerProvider = provider;
this._FeeRateProviderFactory = feeRateProviderFactory;
_networkProvider = networkProvider;
_FeeRateProviderFactory = feeRateProviderFactory;
_WalletProvider = walletProvider;
}
@@ -33,12 +38,106 @@ namespace BTCPayServer.Payments.Bitcoin
public Task<BitcoinAddress> ReserveAddress;
}
public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store, BTCPayNetworkBase network)
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse)
{
var paymentMethodId = new PaymentMethodId(model.CryptoCode, PaymentTypes.BTCLike);
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
var network = _networkProvider.GetNetwork<BTCPayNetwork>(model.CryptoCode);
model.IsLightning = false;
model.PaymentMethodName = GetPaymentMethodName(network);
model.CryptoImage = GetCryptoImage(network);
model.InvoiceBitcoinUrl = cryptoInfo.PaymentUrls.BIP21;
model.InvoiceBitcoinUrlQR = cryptoInfo.PaymentUrls.BIP21;
}
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
return GetCryptoImage(network);
}
private string GetCryptoImage(BTCPayNetworkBase network)
{
return network.CryptoImagePath;
}
public override string GetPaymentMethodName(PaymentMethodId paymentMethodId)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
return GetPaymentMethodName(network);
}
public override async Task<string> IsPaymentMethodAllowedBasedOnInvoiceAmount(StoreBlob storeBlob,
Dictionary<CurrencyPair, Task<RateResult>> rate, Money amount, PaymentMethodId paymentMethodId)
{
if (storeBlob.OnChainMinValue == null)
{
return null;
}
var limitValueRate =
await rate[new CurrencyPair(paymentMethodId.CryptoCode, storeBlob.OnChainMinValue.Currency)];
if (limitValueRate.BidAsk != null)
{
var limitValueCrypto = Money.Coins(storeBlob.OnChainMinValue.Value / limitValueRate.BidAsk.Bid);
if (amount > limitValueCrypto)
{
return null;
}
}
return "The amount of the invoice is too low to be paid on chain";
}
public override IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
{
return _networkProvider.GetAll()
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike));
}
public override CryptoPaymentData GetCryptoPaymentData(PaymentEntity paymentEntity)
{
#pragma warning disable CS0618
BitcoinLikePaymentData paymentData;
if (string.IsNullOrEmpty(paymentEntity.CryptoPaymentDataType))
{
// For invoices created when CryptoPaymentDataType was not existing, we just consider that it is a RBFed payment for safety
paymentData = new BitcoinLikePaymentData();
paymentData.Outpoint = paymentEntity.Outpoint;
paymentData.Output = paymentEntity.Output;
paymentData.RBF = true;
paymentData.ConfirmationCount = 0;
paymentData.Legacy = true;
return paymentData;
}
paymentData =
JsonConvert.DeserializeObject<BitcoinLikePaymentData>(paymentEntity.CryptoPaymentData);
// legacy
paymentData.Output = paymentEntity.Output;
paymentData.Outpoint = paymentEntity.Outpoint;
#pragma warning restore CS0618
return paymentData;
}
private string GetPaymentMethodName(BTCPayNetworkBase network)
{
return network.DisplayName;
}
public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store,
BTCPayNetworkBase network)
{
return new Prepare()
{
GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(),
ReserveAddress = _WalletProvider.GetWallet(network).ReserveAddressAsync(supportedPaymentMethod.AccountDerivation)
ReserveAddress = _WalletProvider.GetWallet(network)
.ReserveAddressAsync(supportedPaymentMethod.AccountDerivation)
};
}
@@ -47,12 +146,13 @@ namespace BTCPayServer.Payments.Bitcoin
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
DerivationSchemeSettings supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
BTCPayNetworkBase network, object preparePaymentObject)
BTCPayNetwork network, object preparePaymentObject)
{
if (!_ExplorerProvider.IsAvailable(network))
throw new PaymentMethodUnavailableException($"Full node not available");
var prepare = (Prepare)preparePaymentObject;
Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod = new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod();
Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod =
new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod();
onchainMethod.NetworkFeeMode = store.GetStoreBlob().NetworkFeeMode;
onchainMethod.FeeRate = await prepare.GetFeeRate;
switch (onchainMethod.NetworkFeeMode)
@@ -68,5 +168,35 @@ namespace BTCPayServer.Payments.Bitcoin
onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
return onchainMethod;
}
public override void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
InvoiceCryptoInfo invoiceCryptoInfo,
PaymentMethodAccounting accounting, PaymentMethod info)
{
var scheme = info.Network.UriScheme;
var minerInfo = new MinerFeeInfo();
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)info.GetPaymentMethodDetails()).FeeRate
.GetFee(1).Satoshi;
invoiceResponse.MinerFees.TryAdd(invoiceCryptoInfo.CryptoCode, minerInfo);
invoiceCryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
{
BIP21 = $"{scheme}:{invoiceCryptoInfo.Address}?amount={invoiceCryptoInfo.Due}",
};
#pragma warning disable 618
if (info.CryptoCode == "BTC")
{
invoiceResponse.BTCPrice = invoiceCryptoInfo.Price;
invoiceResponse.Rate = invoiceCryptoInfo.Rate;
invoiceResponse.ExRates = invoiceCryptoInfo.ExRates;
invoiceResponse.BitcoinAddress = invoiceCryptoInfo.Address;
invoiceResponse.BTCPaid = invoiceCryptoInfo.Paid;
invoiceResponse.BTCDue = invoiceCryptoInfo.Due;
invoiceResponse.PaymentUrls = invoiceCryptoInfo.PaymentUrls;
}
#pragma warning restore 618
}
}
}

View File

@@ -37,7 +37,8 @@ namespace BTCPayServer.Payments.Bitcoin
public NBXplorerListener(ExplorerClientProvider explorerClients,
BTCPayWalletProvider wallets,
InvoiceRepository invoiceRepository,
EventAggregator aggregator, Microsoft.Extensions.Hosting.IApplicationLifetime lifetime)
EventAggregator aggregator,
Microsoft.Extensions.Hosting.IApplicationLifetime lifetime)
{
PollInterval = TimeSpan.FromMinutes(1.0);
_Wallets = wallets;

View File

@@ -1,9 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Rating;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using NBitcoin;
using NBitpayClient;
using InvoiceResponse = BTCPayServer.Models.InvoiceResponse;
namespace BTCPayServer.Payments
{
@@ -21,7 +26,8 @@ namespace BTCPayServer.Payments
/// <param name="network"></param>
/// <param name="preparePaymentObject"></param>
/// <returns></returns>
Task<IPaymentMethodDetails> CreatePaymentMethodDetails(ISupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, BTCPayNetworkBase network, object preparePaymentObject);
Task<IPaymentMethodDetails> CreatePaymentMethodDetails(ISupportedPaymentMethod supportedPaymentMethod,
PaymentMethod paymentMethod, StoreData store, BTCPayNetworkBase network, object preparePaymentObject);
/// <summary>
/// This method called before the rate have been fetched
@@ -32,57 +38,92 @@ namespace BTCPayServer.Payments
/// <returns></returns>
object PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store, BTCPayNetworkBase network);
bool CanHandle(PaymentMethodId paymentMethodId);
void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
InvoiceCryptoInfo invoiceCryptoInfo,
PaymentMethodAccounting accounting, PaymentMethod info);
string ToPrettyString(PaymentMethodId paymentMethodId);
void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse);
string GetCryptoImage(PaymentMethodId paymentMethodId);
string GetPaymentMethodName(PaymentMethodId paymentMethodId);
Task<string> IsPaymentMethodAllowedBasedOnInvoiceAmount(StoreBlob storeBlob,
Dictionary<CurrencyPair, Task<RateResult>> rate,
Money amount, PaymentMethodId paymentMethodId);
IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
CryptoPaymentData GetCryptoPaymentData(PaymentEntity paymentEntity);
}
public interface IPaymentMethodHandler<T> : IPaymentMethodHandler where T : ISupportedPaymentMethod
public interface IPaymentMethodHandler<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler
where TSupportedPaymentMethod : ISupportedPaymentMethod
where TBTCPayNetwork : BTCPayNetworkBase
{
Task<IPaymentMethodDetails> CreatePaymentMethodDetails(T supportedPaymentMethod, PaymentMethod paymentMethod,
StoreData store, BTCPayNetworkBase network, object preparePaymentObject);
Task<IPaymentMethodDetails> CreatePaymentMethodDetails(TSupportedPaymentMethod supportedPaymentMethod,
PaymentMethod paymentMethod, StoreData store, TBTCPayNetwork network, object preparePaymentObject);
}
public abstract class PaymentMethodHandlerBase<T> : IPaymentMethodHandler<T> where T : ISupportedPaymentMethod
public abstract class
PaymentMethodHandlerBase<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler<
TSupportedPaymentMethod, TBTCPayNetwork>
where TSupportedPaymentMethod : ISupportedPaymentMethod
where TBTCPayNetwork : BTCPayNetworkBase
{
public abstract string PrettyDescription { get; }
public abstract PaymentTypes PaymentType { get; }
public abstract Task<IPaymentMethodDetails> CreatePaymentMethodDetails(T supportedPaymentMethod,
PaymentMethod paymentMethod, StoreData store, BTCPayNetworkBase network, object preparePaymentObject);
public virtual object PreparePayment(T supportedPaymentMethod, StoreData store, BTCPayNetworkBase network)
public abstract Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
TSupportedPaymentMethod supportedPaymentMethod,
PaymentMethod paymentMethod, StoreData store, TBTCPayNetwork network, object preparePaymentObject);
public abstract void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
InvoiceCryptoInfo invoiceCryptoInfo, PaymentMethodAccounting accounting, PaymentMethod info);
public abstract void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse);
public abstract string GetCryptoImage(PaymentMethodId paymentMethodId);
public abstract string GetPaymentMethodName(PaymentMethodId paymentMethodId);
public abstract Task<string> IsPaymentMethodAllowedBasedOnInvoiceAmount(StoreBlob storeBlob,
Dictionary<CurrencyPair, Task<RateResult>> rate, Money amount, PaymentMethodId paymentMethodId);
public abstract IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
public abstract CryptoPaymentData GetCryptoPaymentData(PaymentEntity paymentEntity);
public virtual object PreparePayment(TSupportedPaymentMethod supportedPaymentMethod, StoreData store,
BTCPayNetworkBase network)
{
return null;
}
public Task<IPaymentMethodDetails> CreatePaymentMethodDetails(ISupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod,
StoreData store, BTCPayNetworkBase network, object preparePaymentObject)
{
if (supportedPaymentMethod is TSupportedPaymentMethod method && network is TBTCPayNetwork correctNetwork)
{
return CreatePaymentMethodDetails(method, paymentMethod, store, correctNetwork, preparePaymentObject);
}
throw new NotSupportedException("Invalid supportedPaymentMethod");
}
object IPaymentMethodHandler.PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store,
BTCPayNetworkBase network)
{
if (supportedPaymentMethod is T method)
if (supportedPaymentMethod is TSupportedPaymentMethod method)
{
return PreparePayment(method, store, network);
}
throw new NotSupportedException("Invalid supportedPaymentMethod");
}
public bool CanHandle(PaymentMethodId paymentMethodId)
{
return paymentMethodId.PaymentType.Equals(PaymentType);
throw new NotSupportedException("Invalid supportedPaymentMethod");
}
public string ToPrettyString(PaymentMethodId paymentMethodId)
{
return $"{paymentMethodId.CryptoCode} ({PrettyDescription})";
}
Task<IPaymentMethodDetails> IPaymentMethodHandler.CreatePaymentMethodDetails(
ISupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
BTCPayNetworkBase network, object preparePaymentObject)
{
if (supportedPaymentMethod is T method)
{
return CreatePaymentMethodDetails(method, paymentMethod, store, network, preparePaymentObject);
}
throw new NotSupportedException("Invalid supportedPaymentMethod");
}
}
}

View File

@@ -7,8 +7,8 @@ using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Payments
{
/// <summary>
/// This class represent a mode of payment supported by a store.
/// It is stored at the store level and cloned to the invoice during invoice creation.
/// A class for configuration of a type of payment method stored on a store level.
/// It is cloned to invoices of the store during invoice creation.
/// This object will be serialized in database in json
/// </summary>
public interface ISupportedPaymentMethod

View File

@@ -8,27 +8,36 @@ using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Rating;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services;
using BTCPayServer.Services.Rates;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Lightning
{
public class LightningLikePaymentHandler : PaymentMethodHandlerBase<LightningSupportedPaymentMethod>
public class LightningLikePaymentHandler : PaymentMethodHandlerBase<LightningSupportedPaymentMethod, BTCPayNetwork>
{
public static int LIGHTNING_TIMEOUT = 5000;
NBXplorerDashboard _Dashboard;
private readonly LightningClientFactoryService _lightningClientFactory;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly SocketFactory _socketFactory;
public LightningLikePaymentHandler(
NBXplorerDashboard dashboard,
LightningClientFactoryService lightningClientFactory,
BTCPayNetworkProvider networkProvider,
SocketFactory socketFactory)
{
_Dashboard = dashboard;
_lightningClientFactory = lightningClientFactory;
_networkProvider = networkProvider;
_socketFactory = socketFactory;
}
@@ -36,7 +45,7 @@ namespace BTCPayServer.Payments.Lightning
public override PaymentTypes PaymentType => PaymentTypes.LightningLike;
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
BTCPayNetworkBase network, object preparePaymentObject)
BTCPayNetwork network, object preparePaymentObject)
{
//direct casting to (BTCPayNetwork) is fixed in other pull requests with better generic interfacing for handlers
var storeBlob = store.GetStoreBlob();
@@ -131,5 +140,83 @@ namespace BTCPayServer.Payments.Lightning
throw new PaymentMethodUnavailableException($"Error while connecting to the lightning node via {nodeInfo.Host}:{nodeInfo.Port} ({ex.Message})");
}
}
public override IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
{
return _networkProvider.GetAll()
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike));
}
public override async Task<string> IsPaymentMethodAllowedBasedOnInvoiceAmount(StoreBlob storeBlob,
Dictionary<CurrencyPair, Task<RateResult>> rate, Money amount, PaymentMethodId paymentMethodId)
{
if (storeBlob.OnChainMinValue == null)
{
return null;
}
var limitValueRate = await rate[new CurrencyPair(paymentMethodId.CryptoCode, storeBlob.OnChainMinValue.Currency)];
if (limitValueRate.BidAsk != null)
{
var limitValueCrypto = Money.Coins(storeBlob.OnChainMinValue.Value / limitValueRate.BidAsk.Bid);
if (amount < limitValueCrypto)
{
return null;
}
}
return "The amount of the invoice is too high to be paid with lightning";
}
public override CryptoPaymentData GetCryptoPaymentData(PaymentEntity paymentEntity)
{
#pragma warning disable CS0618
return JsonConvert.DeserializeObject<LightningLikePaymentData>(paymentEntity.CryptoPaymentData);
#pragma warning restore CS0618
}
public override void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
InvoiceCryptoInfo invoiceCryptoInfo, PaymentMethodAccounting accounting, PaymentMethod info)
{
invoiceCryptoInfo.PaymentUrls = new InvoicePaymentUrls()
{
BOLT11 = $"lightning:{invoiceCryptoInfo.Address}"
};
}
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse)
{
var paymentMethodId = new PaymentMethodId(model.CryptoCode, PaymentTypes.LightningLike);
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
var network = _networkProvider.GetNetwork<BTCPayNetwork>(model.CryptoCode);
model.IsLightning = true;
model.PaymentMethodName = GetPaymentMethodName(network);
model.CryptoImage = GetCryptoImage(network);
model.InvoiceBitcoinUrl = cryptoInfo.PaymentUrls.BOLT11;
model.InvoiceBitcoinUrlQR = cryptoInfo.PaymentUrls.BOLT11.ToUpperInvariant();
}
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
return GetCryptoImage(network);
}
private string GetCryptoImage(BTCPayNetworkBase network)
{
return ((BTCPayNetwork)network).LightningImagePath;
}
public override string GetPaymentMethodName(PaymentMethodId paymentMethodId)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
return GetPaymentMethodName(network);
}
private string GetPaymentMethodName(BTCPayNetworkBase network)
{
return $"{network.DisplayName} (Lightning)";
}
}
}

View File

@@ -12,6 +12,7 @@ namespace BTCPayServer.Payments
{
public static ISupportedPaymentMethod Deserialize(PaymentMethodId paymentMethodId, JToken value, BTCPayNetworkBase network)
{
//Todo: Abstract
if (paymentMethodId.PaymentType == PaymentTypes.BTCLike)
{
var bitcoinSpecificBtcPayNetwork = (BTCPayNetwork)network;
@@ -37,6 +38,7 @@ namespace BTCPayServer.Payments
public static IPaymentMethodDetails DeserializePaymentMethodDetails(PaymentMethodId paymentMethodId, JObject jobj)
{
//Todo: Abstract
if(paymentMethodId.PaymentType == PaymentTypes.BTCLike)
{
return JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod>(jobj.ToString());

View File

@@ -16,7 +16,7 @@ namespace BTCPayServer.Payments
if (cryptoCode == null)
throw new ArgumentNullException(nameof(cryptoCode));
PaymentType = paymentType;
CryptoCode = cryptoCode;
CryptoCode = cryptoCode.ToUpperInvariant();
}
[Obsolete("Should only be used for legacy stuff")]
@@ -62,6 +62,7 @@ namespace BTCPayServer.Payments
public override string ToString()
{
//BTCLike case is special because it is in legacy mode.
return PaymentType == PaymentTypes.BTCLike ? CryptoCode : $"{CryptoCode}_{PaymentType}";
}
@@ -74,7 +75,8 @@ namespace BTCPayServer.Payments
PaymentTypes type = PaymentTypes.BTCLike;
if (parts.Length == 2)
{
switch (parts[1].ToLowerInvariant())
var typePart = parts[1].ToLowerInvariant();
switch (typePart)
{
case "btclike":
case "onchain":
@@ -85,8 +87,13 @@ namespace BTCPayServer.Payments
type = PaymentTypes.LightningLike;
break;
default:
if (!Enum.TryParse(typePart, true, out type ))
{
return false;
}
break;
}
}
paymentMethodId = new PaymentMethodId(parts[0], type);
return true;

View File

@@ -68,6 +68,8 @@ namespace BTCPayServer.Services
}
}
public HttpContext Context => httpContext.HttpContext;
public override string ToString()
{
StringBuilder txt = new StringBuilder();

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
@@ -77,6 +78,7 @@ namespace BTCPayServer.Services.Invoices.Export
PaymentId = pdata.GetPaymentId(),
CryptoCode = cryptoCode,
ConversionRate = pmethod.Rate,
//TODO: Abstract, we should use Payment Type or Pretty Description from handler
PaymentType = payment.GetPaymentMethodId().PaymentType == Payments.PaymentTypes.BTCLike ? "OnChain" : "OffChain",
Destination = payment.GetCryptoPaymentData().GetDestination(Networks.GetNetwork<BTCPayNetworkBase>(cryptoCode)),
Paid = pdata.GetValue().ToString(CultureInfo.InvariantCulture),

View File

@@ -4,6 +4,7 @@ using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Models;
using Newtonsoft.Json.Linq;
using NBitcoin.DataEncoders;
@@ -113,6 +114,9 @@ namespace BTCPayServer.Services.Invoices
}
public class InvoiceEntity
{
[JsonIgnore]
public PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary { get; set; }
[JsonIgnore]
public BTCPayNetworkProvider Networks { get; set; }
public const int InternalTagSupport_Version = 1;
@@ -421,7 +425,6 @@ namespace BTCPayServer.Services.Invoices
cryptoInfo.ExRates = exrates;
var paymentId = info.GetId();
var scheme = info.Network.UriScheme;
cryptoInfo.Url = ServerUrl.WithTrailingSlash() + $"i/{paymentId}/{Id}";
cryptoInfo.Payments = GetPayments(info.Network).Select(entity =>
@@ -440,39 +443,8 @@ namespace BTCPayServer.Services.Invoices
};
}).ToList();
if (paymentId.PaymentType == PaymentTypes.BTCLike)
{
var minerInfo = new MinerFeeInfo();
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)info.GetPaymentMethodDetails()).FeeRate.GetFee(1).Satoshi;
dto.MinerFees.TryAdd(paymentId.CryptoCode, minerInfo);
var cryptoSuffix = cryptoInfo.CryptoCode == "BTC" ? "" : "/" + cryptoInfo.CryptoCode;
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
{
BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}",
};
}
PaymentMethodHandlerDictionary[paymentId].PrepareInvoiceDto(dto, this, cryptoInfo, accounting, info);
if (paymentId.PaymentType == PaymentTypes.LightningLike)
{
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
{
BOLT11 = $"lightning:{cryptoInfo.Address}"
};
}
#pragma warning disable CS0618
if (info.CryptoCode == "BTC" && paymentId.PaymentType == PaymentTypes.BTCLike)
{
dto.BTCPrice = cryptoInfo.Price;
dto.Rate = cryptoInfo.Rate;
dto.ExRates = cryptoInfo.ExRates;
dto.BitcoinAddress = cryptoInfo.Address;
dto.BTCPaid = cryptoInfo.Paid;
dto.BTCDue = cryptoInfo.Due;
dto.PaymentUrls = cryptoInfo.PaymentUrls;
}
#pragma warning restore CS0618
dto.CryptoInfo.Add(cryptoInfo);
dto.PaymentCodes.Add(paymentId.ToString(), cryptoInfo.PaymentUrls);
dto.PaymentSubtotals.Add(paymentId.ToString(), subtotalPrice.Satoshi);
@@ -842,8 +814,8 @@ namespace BTCPayServer.Services.Invoices
var totalDueNoNetworkCost = Money.Coins(Extensions.RoundUp(totalDue, precision));
bool paidEnough = paid >= Extensions.RoundUp(totalDue, precision);
int txRequired = 0;
var payments =
ParentEntity.GetPayments()
_ = ParentEntity.GetPayments()
.Where(p => p.Accounted && paymentPredicate(p))
.OrderBy(p => p.ReceivedTime)
.Select(_ =>
@@ -854,15 +826,16 @@ namespace BTCPayServer.Services.Invoices
{
totalDue += txFee;
}
paidEnough |= Extensions.RoundUp(paid, precision) >= Extensions.RoundUp(totalDue, precision);
if (GetId() == _.GetPaymentMethodId())
{
cryptoPaid += _.GetCryptoPaymentData().GetValue();
txRequired++;
}
return _;
})
.ToArray();
}).ToArray();
var accounting = new PaymentMethodAccounting();
accounting.TxCount = txRequired;
@@ -895,6 +868,8 @@ namespace BTCPayServer.Services.Invoices
public class PaymentEntity
{
[JsonIgnore]
public PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary { get; set; }
public int Version { get; set; }
public DateTimeOffset ReceivedTime
{
@@ -934,33 +909,8 @@ namespace BTCPayServer.Services.Invoices
public CryptoPaymentData GetCryptoPaymentData()
{
#pragma warning disable CS0618
if (string.IsNullOrEmpty(CryptoPaymentDataType))
{
// For invoices created when CryptoPaymentDataType was not existing, we just consider that it is a RBFed payment for safety
var paymentData = new Payments.Bitcoin.BitcoinLikePaymentData();
paymentData.Outpoint = Outpoint;
paymentData.Output = Output;
paymentData.RBF = true;
paymentData.ConfirmationCount = 0;
paymentData.Legacy = true;
return paymentData;
}
if (GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike)
{
var paymentData = JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikePaymentData>(CryptoPaymentData);
// legacy
paymentData.Output = Output;
paymentData.Outpoint = Outpoint;
return paymentData;
}
if (GetPaymentMethodId().PaymentType == PaymentTypes.LightningLike)
{
return JsonConvert.DeserializeObject<Payments.Lightning.LightningLikePaymentData>(CryptoPaymentData);
}
throw new NotSupportedException(nameof(CryptoPaymentDataType) + " does not support " + CryptoPaymentDataType);
#pragma warning restore CS0618
var paymentMethodId = GetPaymentMethodId();
return PaymentMethodHandlerDictionary[paymentMethodId].GetCryptoPaymentData(this);
}
public PaymentEntity SetCryptoPaymentData(CryptoPaymentData cryptoPaymentData)
@@ -980,6 +930,7 @@ namespace BTCPayServer.Services.Invoices
}
internal decimal GetValue(PaymentMethodDictionary paymentMethods, PaymentMethodId paymentMethodId, decimal? value = null)
{
value = value ?? this.GetCryptoPaymentData().GetValue();
var to = paymentMethodId;
var from = this.GetPaymentMethodId();
@@ -1007,7 +958,9 @@ namespace BTCPayServer.Services.Invoices
#pragma warning restore CS0618
}
}
/// <summary>
/// A record of a payment
/// </summary>
public interface CryptoPaymentData
{
/// <summary>

View File

@@ -1,4 +1,4 @@
using DBriize;
using DBriize;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
@@ -38,8 +38,11 @@ namespace BTCPayServer.Services.Invoices
private ApplicationDbContextFactory _ContextFactory;
private readonly BTCPayNetworkProvider _Networks;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private CustomThreadPool _IndexerThread;
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath, BTCPayNetworkProvider networks)
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath,
BTCPayNetworkProvider networks, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
{
int retryCount = 0;
retry:
@@ -51,6 +54,18 @@ retry:
_IndexerThread = new CustomThreadPool(1, "Invoice Indexer");
_ContextFactory = contextFactory;
_Networks = networks;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
}
public InvoiceEntity CreateNewInvoice()
{
return new InvoiceEntity()
{
PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary,
Networks = _Networks,
Version = InvoiceEntity.Lastest_Version,
InvoiceTime = DateTimeOffset.UtcNow,
};
}
public async Task<bool> RemovePendingInvoice(string invoiceId)
@@ -138,6 +153,7 @@ retry:
{
List<string> textSearch = new List<string>();
invoice = ToObject(ToBytes(invoice));
invoice.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
invoice.Networks = _Networks;
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
#pragma warning disable CS0618
@@ -430,7 +446,7 @@ retry:
{
var paymentEntity = ToObject<PaymentEntity>(p.Blob, null);
paymentEntity.Accounted = p.Accounted;
paymentEntity.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
// PaymentEntity on version 0 does not have their own fee, because it was assumed that the payment method have fixed fee.
// We want to hide this legacy detail in InvoiceRepository, so we fetch the fee from the PaymentMethod and assign it to the PaymentEntity.
if (paymentEntity.Version == 0)
@@ -635,6 +651,7 @@ retry:
if (invoice == null)
return null;
InvoiceEntity invoiceEntity = ToObject(invoice.Blob);
invoiceEntity.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()), null);
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
PaymentEntity entity = new PaymentEntity
@@ -645,7 +662,8 @@ retry:
#pragma warning restore CS0618
ReceivedTime = date.UtcDateTime,
Accounted = accounted,
NetworkFee = paymentMethodDetails.GetNextNetworkFee()
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary
};
entity.SetCryptoPaymentData(paymentData);
@@ -702,6 +720,7 @@ retry:
private InvoiceEntity ToObject(byte[] value)
{
var entity = NBitcoin.JsonConverters.Serializer.ToObject<InvoiceEntity>(ZipUtils.Unzip(value), null);
entity.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
entity.Networks = _Networks;
return entity;
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Payments;

View File

@@ -0,0 +1,34 @@
using System.Collections;
using System.Collections.Generic;
using BTCPayServer.Payments;
namespace BTCPayServer.Services.Invoices
{
public class PaymentMethodHandlerDictionary : IEnumerable<IPaymentMethodHandler>
{
private readonly Dictionary<PaymentMethodId, IPaymentMethodHandler> _mappedHandlers =
new Dictionary<PaymentMethodId, IPaymentMethodHandler>();
public PaymentMethodHandlerDictionary(IEnumerable<IPaymentMethodHandler> paymentMethodHandlers)
{
foreach (var paymentMethodHandler in paymentMethodHandlers)
{
foreach (var supportedPaymentMethod in paymentMethodHandler.GetSupportedPaymentMethods())
{
_mappedHandlers.Add(supportedPaymentMethod, paymentMethodHandler);
}
}
}
public IPaymentMethodHandler this[PaymentMethodId index] => _mappedHandlers[index];
public IEnumerator<IPaymentMethodHandler> GetEnumerator()
{
return _mappedHandlers.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}