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>(); 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 public T GetController<T>(string userId = null, string storeId = null, Claim[] additionalClaims = null) where T : Controller
{ {
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();

View File

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

View File

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

View File

@@ -19,10 +19,8 @@ using BTCPayServer.Security;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Invoices.Export; using BTCPayServer.Services.Invoices.Export;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore.Internal;
using NBitcoin; using NBitcoin;
using NBitpayClient; using NBitpayClient;
using NBXplorer; using NBXplorer;
@@ -77,12 +75,15 @@ namespace BTCPayServer.Controllers
StatusMessage = StatusMessage 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()), Destination = h.GetAddress(),
Current = !h.UnAssigned.HasValue PaymentMethod =
}).ToArray(); invoice.PaymentMethodHandlerDictionary[h.GetPaymentMethodId()]
.ToPrettyString(h.GetPaymentMethodId()),
Current = !h.UnAssigned.HasValue
}).ToArray();
var details = InvoicePopulatePayments(invoice); var details = InvoicePopulatePayments(invoice);
model.CryptoPayments = details.CryptoPayments; model.CryptoPayments = details.CryptoPayments;
@@ -101,11 +102,12 @@ namespace BTCPayServer.Controllers
var accounting = data.Calculate(); var accounting = data.Calculate();
var paymentMethodId = data.GetId(); var paymentMethodId = data.GetId();
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment(); 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.Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
cryptoPayment.Paid = _CurrencyNameTable.DisplayFormatCurrency(accounting.CryptoPaid.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); cryptoPayment.Overpaid = _CurrencyNameTable.DisplayFormatCurrency(accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
//TODO: abstract
var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod; var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
if (onchainMethod != null) if (onchainMethod != null)
{ {
@@ -160,21 +162,6 @@ namespace BTCPayServer.Controllers
return model; 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] [HttpGet]
[Route("i/{invoiceId}")] [Route("i/{invoiceId}")]
[Route("i/{invoiceId}/{paymentMethodId}")] [Route("i/{invoiceId}/{paymentMethodId}")]
@@ -229,7 +216,7 @@ namespace BTCPayServer.Controllers
return View(model); return View(model);
} }
//TODO: abstract
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId) private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId)
{ {
var invoice = await _InvoiceRepository.GetInvoice(invoiceId); var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
@@ -245,6 +232,7 @@ namespace BTCPayServer.Controllers
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode); BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
if (network == null && isDefaultPaymentId) 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(); network = _NetworkProvider.GetAll().OfType<BTCPayNetwork>().FirstOrDefault();
paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike); paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
} }
@@ -291,10 +279,6 @@ namespace BTCPayServer.Controllers
{ {
CryptoCode = network.CryptoCode, CryptoCode = network.CryptoCode,
RootPath = this.Request.PathBase.Value.WithTrailingSlash(), 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, OrderId = invoice.OrderId,
InvoiceId = invoice.Id, InvoiceId = invoice.Id,
DefaultLang = storeBlob.DefaultLang ?? "en", DefaultLang = storeBlob.DefaultLang ?? "en",
@@ -316,13 +300,7 @@ namespace BTCPayServer.Controllers
MerchantRefLink = invoice.RedirectURL ?? "/", MerchantRefLink = invoice.RedirectURL ?? "/",
RedirectAutomatically = invoice.RedirectAutomatically, RedirectAutomatically = invoice.RedirectAutomatically,
StoreName = store.StoreName, 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, 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, TxCount = accounting.TxRequired,
BtcPaid = accounting.Paid.ToString(), BtcPaid = accounting.Paid.ToString(),
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
@@ -340,37 +318,38 @@ namespace BTCPayServer.Controllers
StoreId = store.Id, StoreId = store.Id,
AvailableCryptos = invoice.GetPaymentMethods() AvailableCryptos = invoice.GetPaymentMethods()
.Where(i => i.Network != null) .Where(i => i.Network != null)
.Select(kv => new PaymentModel.AvailableCrypto() .Select(kv =>
{ {
PaymentMethodId = kv.GetId().ToString(), var availableCryptoPaymentMethodId = kv.GetId();
CryptoCode = kv.GetId().CryptoCode, var availableCryptoHandler =
PaymentMethodName = GetDisplayName(kv.GetId(), kv.Network), invoice.PaymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
IsLightning = kv.GetId().PaymentType == PaymentTypes.LightningLike, return new PaymentModel.AvailableCrypto()
CryptoImage = GetImage(kv.GetId(), kv.Network), {
Link = Url.Action(nameof(Checkout), new { invoiceId = invoiceId, paymentMethodId = kv.GetId().ToString() }) PaymentMethodId = kv.GetId().ToString(),
CryptoCode = kv.GetId().CryptoCode,
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 != "/") }).Where(c => c.CryptoImage != "/")
.OrderByDescending(a => a.CryptoCode == "BTC").ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0) .OrderByDescending(a => a.CryptoCode == "BTC").ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
.ToList() .ToList()
}; };
invoice.PaymentMethodHandlerDictionary[paymentMethod.GetId()].PreparePaymentModel(model, dto);
model.PaymentMethodId = paymentMethodId.ToString();
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds); var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
model.TimeLeft = expiration.PrettyPrint(); model.TimeLeft = expiration.PrettyPrint();
return model; 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) 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" // 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() private SelectList GetPaymentMethodsSelectList()
{ {
return new SelectList( return new SelectList(_paymentMethodHandlerDictionary.Distinct().SelectMany(handler =>
_NetworkProvider.GetAll() handler.GetSupportedPaymentMethods()
.SelectMany(network => new[] .Select(id => new SelectListItem(handler.ToPrettyString(id), id.ToString()))),
{
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());
}),
nameof(SelectListItem.Value), nameof(SelectListItem.Value),
nameof(SelectListItem.Text)); nameof(SelectListItem.Text));
} }
[HttpGet] [HttpGet]
[Route("invoices/create")] [Route("invoices/create")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]

View File

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

View File

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

View File

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

View File

@@ -39,11 +39,6 @@ namespace BTCPayServer
{ {
public static class Extensions 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) public static string PrettyPrint(this TimeSpan expiration)
{ {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Models.PaymentRequestViewModels; using BTCPayServer.Models.PaymentRequestViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; 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.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Rating;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using NBitcoin; using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Bitcoin namespace BTCPayServer.Payments.Bitcoin
{ {
public class BitcoinLikePaymentHandler : PaymentMethodHandlerBase<DerivationSchemeSettings> public class BitcoinLikePaymentHandler : PaymentMethodHandlerBase<DerivationSchemeSettings, BTCPayNetwork>
{ {
ExplorerClientProvider _ExplorerProvider; ExplorerClientProvider _ExplorerProvider;
private readonly BTCPayNetworkProvider _networkProvider;
private IFeeProviderFactory _FeeRateProviderFactory; private IFeeProviderFactory _FeeRateProviderFactory;
private Services.Wallets.BTCPayWalletProvider _WalletProvider; private Services.Wallets.BTCPayWalletProvider _WalletProvider;
public BitcoinLikePaymentHandler(ExplorerClientProvider provider, public BitcoinLikePaymentHandler(ExplorerClientProvider provider,
IFeeProviderFactory feeRateProviderFactory, BTCPayNetworkProvider networkProvider,
Services.Wallets.BTCPayWalletProvider walletProvider) IFeeProviderFactory feeRateProviderFactory,
Services.Wallets.BTCPayWalletProvider walletProvider)
{ {
if (provider == null)
throw new ArgumentNullException(nameof(provider));
_ExplorerProvider = provider; _ExplorerProvider = provider;
this._FeeRateProviderFactory = feeRateProviderFactory; _networkProvider = networkProvider;
_FeeRateProviderFactory = feeRateProviderFactory;
_WalletProvider = walletProvider; _WalletProvider = walletProvider;
} }
@@ -33,12 +38,106 @@ namespace BTCPayServer.Payments.Bitcoin
public Task<BitcoinAddress> ReserveAddress; 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() return new Prepare()
{ {
GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(), 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( public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
DerivationSchemeSettings supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, DerivationSchemeSettings supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
BTCPayNetworkBase network, object preparePaymentObject) BTCPayNetwork network, object preparePaymentObject)
{ {
if (!_ExplorerProvider.IsAvailable(network)) if (!_ExplorerProvider.IsAvailable(network))
throw new PaymentMethodUnavailableException($"Full node not available"); throw new PaymentMethodUnavailableException($"Full node not available");
var prepare = (Prepare)preparePaymentObject; 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.NetworkFeeMode = store.GetStoreBlob().NetworkFeeMode;
onchainMethod.FeeRate = await prepare.GetFeeRate; onchainMethod.FeeRate = await prepare.GetFeeRate;
switch (onchainMethod.NetworkFeeMode) switch (onchainMethod.NetworkFeeMode)
@@ -68,5 +168,35 @@ namespace BTCPayServer.Payments.Bitcoin
onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString(); onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
return onchainMethod; 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, public NBXplorerListener(ExplorerClientProvider explorerClients,
BTCPayWalletProvider wallets, BTCPayWalletProvider wallets,
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
EventAggregator aggregator, Microsoft.Extensions.Hosting.IApplicationLifetime lifetime) EventAggregator aggregator,
Microsoft.Extensions.Hosting.IApplicationLifetime lifetime)
{ {
PollInterval = TimeSpan.FromMinutes(1.0); PollInterval = TimeSpan.FromMinutes(1.0);
_Wallets = wallets; _Wallets = wallets;

View File

@@ -1,9 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Rating;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using NBitcoin;
using NBitpayClient;
using InvoiceResponse = BTCPayServer.Models.InvoiceResponse;
namespace BTCPayServer.Payments namespace BTCPayServer.Payments
{ {
@@ -21,7 +26,8 @@ namespace BTCPayServer.Payments
/// <param name="network"></param> /// <param name="network"></param>
/// <param name="preparePaymentObject"></param> /// <param name="preparePaymentObject"></param>
/// <returns></returns> /// <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> /// <summary>
/// This method called before the rate have been fetched /// This method called before the rate have been fetched
@@ -31,58 +37,93 @@ namespace BTCPayServer.Payments
/// <param name="network"></param> /// <param name="network"></param>
/// <returns></returns> /// <returns></returns>
object PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store, BTCPayNetworkBase network); 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); 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, Task<IPaymentMethodDetails> CreatePaymentMethodDetails(TSupportedPaymentMethod supportedPaymentMethod,
StoreData store, BTCPayNetworkBase network, object preparePaymentObject); 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 string PrettyDescription { get; }
public abstract PaymentTypes PaymentType { get; } public abstract PaymentTypes PaymentType { get; }
public abstract Task<IPaymentMethodDetails> CreatePaymentMethodDetails(T supportedPaymentMethod,
PaymentMethod paymentMethod, StoreData store, BTCPayNetworkBase network, object preparePaymentObject); public abstract Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
public virtual object PreparePayment(T supportedPaymentMethod, StoreData store, BTCPayNetworkBase network) 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; 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, object IPaymentMethodHandler.PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store,
BTCPayNetworkBase network) BTCPayNetworkBase network)
{ {
if (supportedPaymentMethod is T method) if (supportedPaymentMethod is TSupportedPaymentMethod method)
{ {
return PreparePayment(method, store, network); return PreparePayment(method, store, network);
} }
throw new NotSupportedException("Invalid supportedPaymentMethod");
}
public bool CanHandle(PaymentMethodId paymentMethodId) throw new NotSupportedException("Invalid supportedPaymentMethod");
{
return paymentMethodId.PaymentType.Equals(PaymentType);
} }
public string ToPrettyString(PaymentMethodId paymentMethodId) public string ToPrettyString(PaymentMethodId paymentMethodId)
{ {
return $"{paymentMethodId.CryptoCode} ({PrettyDescription})"; 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 namespace BTCPayServer.Payments
{ {
/// <summary> /// <summary>
/// This class represent a mode of payment supported by a store. /// A class for configuration of a type of payment method stored on a store level.
/// It is stored at the store level and cloned to the invoice during invoice creation. /// It is cloned to invoices of the store during invoice creation.
/// This object will be serialized in database in json /// This object will be serialized in database in json
/// </summary> /// </summary>
public interface ISupportedPaymentMethod public interface ISupportedPaymentMethod

View File

@@ -8,27 +8,36 @@ using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Rating;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Rates;
using NBitcoin; using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
namespace BTCPayServer.Payments.Lightning namespace BTCPayServer.Payments.Lightning
{ {
public class LightningLikePaymentHandler : PaymentMethodHandlerBase<LightningSupportedPaymentMethod> public class LightningLikePaymentHandler : PaymentMethodHandlerBase<LightningSupportedPaymentMethod, BTCPayNetwork>
{ {
public static int LIGHTNING_TIMEOUT = 5000; public static int LIGHTNING_TIMEOUT = 5000;
NBXplorerDashboard _Dashboard; NBXplorerDashboard _Dashboard;
private readonly LightningClientFactoryService _lightningClientFactory; private readonly LightningClientFactoryService _lightningClientFactory;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly SocketFactory _socketFactory; private readonly SocketFactory _socketFactory;
public LightningLikePaymentHandler( public LightningLikePaymentHandler(
NBXplorerDashboard dashboard, NBXplorerDashboard dashboard,
LightningClientFactoryService lightningClientFactory, LightningClientFactoryService lightningClientFactory,
BTCPayNetworkProvider networkProvider,
SocketFactory socketFactory) SocketFactory socketFactory)
{ {
_Dashboard = dashboard; _Dashboard = dashboard;
_lightningClientFactory = lightningClientFactory; _lightningClientFactory = lightningClientFactory;
_networkProvider = networkProvider;
_socketFactory = socketFactory; _socketFactory = socketFactory;
} }
@@ -36,7 +45,7 @@ namespace BTCPayServer.Payments.Lightning
public override PaymentTypes PaymentType => PaymentTypes.LightningLike; public override PaymentTypes PaymentType => PaymentTypes.LightningLike;
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails( public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, 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 //direct casting to (BTCPayNetwork) is fixed in other pull requests with better generic interfacing for handlers
var storeBlob = store.GetStoreBlob(); 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})"); 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) public static ISupportedPaymentMethod Deserialize(PaymentMethodId paymentMethodId, JToken value, BTCPayNetworkBase network)
{ {
//Todo: Abstract
if (paymentMethodId.PaymentType == PaymentTypes.BTCLike) if (paymentMethodId.PaymentType == PaymentTypes.BTCLike)
{ {
var bitcoinSpecificBtcPayNetwork = (BTCPayNetwork)network; var bitcoinSpecificBtcPayNetwork = (BTCPayNetwork)network;
@@ -37,6 +38,7 @@ namespace BTCPayServer.Payments
public static IPaymentMethodDetails DeserializePaymentMethodDetails(PaymentMethodId paymentMethodId, JObject jobj) public static IPaymentMethodDetails DeserializePaymentMethodDetails(PaymentMethodId paymentMethodId, JObject jobj)
{ {
//Todo: Abstract
if(paymentMethodId.PaymentType == PaymentTypes.BTCLike) if(paymentMethodId.PaymentType == PaymentTypes.BTCLike)
{ {
return JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod>(jobj.ToString()); return JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod>(jobj.ToString());

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Models; using BTCPayServer.Models;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
@@ -113,6 +114,9 @@ namespace BTCPayServer.Services.Invoices
} }
public class InvoiceEntity public class InvoiceEntity
{ {
[JsonIgnore]
public PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary { get; set; }
[JsonIgnore] [JsonIgnore]
public BTCPayNetworkProvider Networks { get; set; } public BTCPayNetworkProvider Networks { get; set; }
public const int InternalTagSupport_Version = 1; public const int InternalTagSupport_Version = 1;
@@ -421,7 +425,6 @@ namespace BTCPayServer.Services.Invoices
cryptoInfo.ExRates = exrates; cryptoInfo.ExRates = exrates;
var paymentId = info.GetId(); var paymentId = info.GetId();
var scheme = info.Network.UriScheme;
cryptoInfo.Url = ServerUrl.WithTrailingSlash() + $"i/{paymentId}/{Id}"; cryptoInfo.Url = ServerUrl.WithTrailingSlash() + $"i/{paymentId}/{Id}";
cryptoInfo.Payments = GetPayments(info.Network).Select(entity => cryptoInfo.Payments = GetPayments(info.Network).Select(entity =>
@@ -440,39 +443,8 @@ namespace BTCPayServer.Services.Invoices
}; };
}).ToList(); }).ToList();
if (paymentId.PaymentType == PaymentTypes.BTCLike) PaymentMethodHandlerDictionary[paymentId].PrepareInvoiceDto(dto, this, cryptoInfo, accounting, info);
{
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}",
};
}
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.CryptoInfo.Add(cryptoInfo);
dto.PaymentCodes.Add(paymentId.ToString(), cryptoInfo.PaymentUrls); dto.PaymentCodes.Add(paymentId.ToString(), cryptoInfo.PaymentUrls);
dto.PaymentSubtotals.Add(paymentId.ToString(), subtotalPrice.Satoshi); dto.PaymentSubtotals.Add(paymentId.ToString(), subtotalPrice.Satoshi);
@@ -842,8 +814,8 @@ namespace BTCPayServer.Services.Invoices
var totalDueNoNetworkCost = Money.Coins(Extensions.RoundUp(totalDue, precision)); var totalDueNoNetworkCost = Money.Coins(Extensions.RoundUp(totalDue, precision));
bool paidEnough = paid >= Extensions.RoundUp(totalDue, precision); bool paidEnough = paid >= Extensions.RoundUp(totalDue, precision);
int txRequired = 0; int txRequired = 0;
var payments =
ParentEntity.GetPayments() _ = ParentEntity.GetPayments()
.Where(p => p.Accounted && paymentPredicate(p)) .Where(p => p.Accounted && paymentPredicate(p))
.OrderBy(p => p.ReceivedTime) .OrderBy(p => p.ReceivedTime)
.Select(_ => .Select(_ =>
@@ -854,15 +826,16 @@ namespace BTCPayServer.Services.Invoices
{ {
totalDue += txFee; totalDue += txFee;
} }
paidEnough |= Extensions.RoundUp(paid, precision) >= Extensions.RoundUp(totalDue, precision); paidEnough |= Extensions.RoundUp(paid, precision) >= Extensions.RoundUp(totalDue, precision);
if (GetId() == _.GetPaymentMethodId()) if (GetId() == _.GetPaymentMethodId())
{ {
cryptoPaid += _.GetCryptoPaymentData().GetValue(); cryptoPaid += _.GetCryptoPaymentData().GetValue();
txRequired++; txRequired++;
} }
return _; return _;
}) }).ToArray();
.ToArray();
var accounting = new PaymentMethodAccounting(); var accounting = new PaymentMethodAccounting();
accounting.TxCount = txRequired; accounting.TxCount = txRequired;
@@ -895,6 +868,8 @@ namespace BTCPayServer.Services.Invoices
public class PaymentEntity public class PaymentEntity
{ {
[JsonIgnore]
public PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary { get; set; }
public int Version { get; set; } public int Version { get; set; }
public DateTimeOffset ReceivedTime public DateTimeOffset ReceivedTime
{ {
@@ -934,33 +909,8 @@ namespace BTCPayServer.Services.Invoices
public CryptoPaymentData GetCryptoPaymentData() public CryptoPaymentData GetCryptoPaymentData()
{ {
#pragma warning disable CS0618 var paymentMethodId = GetPaymentMethodId();
if (string.IsNullOrEmpty(CryptoPaymentDataType)) return PaymentMethodHandlerDictionary[paymentMethodId].GetCryptoPaymentData(this);
{
// 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
} }
public PaymentEntity SetCryptoPaymentData(CryptoPaymentData cryptoPaymentData) public PaymentEntity SetCryptoPaymentData(CryptoPaymentData cryptoPaymentData)
@@ -980,6 +930,7 @@ namespace BTCPayServer.Services.Invoices
} }
internal decimal GetValue(PaymentMethodDictionary paymentMethods, PaymentMethodId paymentMethodId, decimal? value = null) internal decimal GetValue(PaymentMethodDictionary paymentMethods, PaymentMethodId paymentMethodId, decimal? value = null)
{ {
value = value ?? this.GetCryptoPaymentData().GetValue(); value = value ?? this.GetCryptoPaymentData().GetValue();
var to = paymentMethodId; var to = paymentMethodId;
var from = this.GetPaymentMethodId(); var from = this.GetPaymentMethodId();
@@ -1007,7 +958,9 @@ namespace BTCPayServer.Services.Invoices
#pragma warning restore CS0618 #pragma warning restore CS0618
} }
} }
/// <summary>
/// A record of a payment
/// </summary>
public interface CryptoPaymentData public interface CryptoPaymentData
{ {
/// <summary> /// <summary>

View File

@@ -1,4 +1,4 @@
using DBriize; using DBriize;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -38,8 +38,11 @@ namespace BTCPayServer.Services.Invoices
private ApplicationDbContextFactory _ContextFactory; private ApplicationDbContextFactory _ContextFactory;
private readonly BTCPayNetworkProvider _Networks; private readonly BTCPayNetworkProvider _Networks;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private CustomThreadPool _IndexerThread; private CustomThreadPool _IndexerThread;
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath, BTCPayNetworkProvider networks)
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath,
BTCPayNetworkProvider networks, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
{ {
int retryCount = 0; int retryCount = 0;
retry: retry:
@@ -51,6 +54,18 @@ retry:
_IndexerThread = new CustomThreadPool(1, "Invoice Indexer"); _IndexerThread = new CustomThreadPool(1, "Invoice Indexer");
_ContextFactory = contextFactory; _ContextFactory = contextFactory;
_Networks = networks; _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) public async Task<bool> RemovePendingInvoice(string invoiceId)
@@ -138,6 +153,7 @@ retry:
{ {
List<string> textSearch = new List<string>(); List<string> textSearch = new List<string>();
invoice = ToObject(ToBytes(invoice)); invoice = ToObject(ToBytes(invoice));
invoice.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
invoice.Networks = _Networks; invoice.Networks = _Networks;
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
#pragma warning disable CS0618 #pragma warning disable CS0618
@@ -430,7 +446,7 @@ retry:
{ {
var paymentEntity = ToObject<PaymentEntity>(p.Blob, null); var paymentEntity = ToObject<PaymentEntity>(p.Blob, null);
paymentEntity.Accounted = p.Accounted; 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. // 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. // 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) if (paymentEntity.Version == 0)
@@ -635,6 +651,7 @@ retry:
if (invoice == null) if (invoice == null)
return null; return null;
InvoiceEntity invoiceEntity = ToObject(invoice.Blob); InvoiceEntity invoiceEntity = ToObject(invoice.Blob);
invoiceEntity.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()), null); PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()), null);
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails(); IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
PaymentEntity entity = new PaymentEntity PaymentEntity entity = new PaymentEntity
@@ -645,7 +662,8 @@ retry:
#pragma warning restore CS0618 #pragma warning restore CS0618
ReceivedTime = date.UtcDateTime, ReceivedTime = date.UtcDateTime,
Accounted = accounted, Accounted = accounted,
NetworkFee = paymentMethodDetails.GetNextNetworkFee() NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary
}; };
entity.SetCryptoPaymentData(paymentData); entity.SetCryptoPaymentData(paymentData);
@@ -702,6 +720,7 @@ retry:
private InvoiceEntity ToObject(byte[] value) private InvoiceEntity ToObject(byte[] value)
{ {
var entity = NBitcoin.JsonConverters.Serializer.ToObject<InvoiceEntity>(ZipUtils.Unzip(value), null); var entity = NBitcoin.JsonConverters.Serializer.ToObject<InvoiceEntity>(ZipUtils.Unzip(value), null);
entity.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
entity.Networks = _Networks; entity.Networks = _Networks;
return entity; return entity;
} }

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Payments; 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();
}
}
}