[WIP] Further abstractions to Payment Handlers (#867)

* mark items to abstract


wip


wip


wip


wip


wip


wip


wip


cleanup


parse other types


compile and fix tests


fix bug 


fix warnings


fix rebase error


reduce payment method handler passings


more cleanup


switch tests to Fast mode 


fix obsolete warning


remove argument requirement 


rebase fixes


remove overcomplicated code


better parsing


remove dependency on environement


remove async

* fixes and simplification

* simplify

* clean up even more

* replace nuglify dependency

* remove extra space

* Fix tests

* fix booboo

* missing setter

* change url resolver

* reduce payment method handlers

* wrap payment method handlers in a custom type

* fix tests

* make invoice controller UI selectlist population cleaner

* make store controller use payment handler dictionary

* fix ln flag

* fix store controller test

* remove null checks on payment handlers

* remove unused imports

* BitcoinSpecificBtcPayNetwork - abstract BTCPayNetwork

* some type fixes

* fix tests

* simplify fetching handler in invoice controller

* rename network base and bitcoin classes

* abstract serializer to network level

* fix serializer when network not provided

* fix serializer when network not provided

* fix serializer when network not provided

* Abstract more payment type specific logic to handlers

* fix merge issue

* small fixes

* make use of repository instead of direct context usage

* reduce redundant code

* sanity check

* test fixes
This commit is contained in:
Andrew Camilleri 2019-05-30 07:02:52 +00:00 committed by Nicolas Dorier
parent 0e568e2af5
commit 916323bb3b
16 changed files with 113 additions and 100 deletions

View File

@ -2252,14 +2252,14 @@ donation:
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Coins(0.001m);
cashCow.SendToAddress(invoiceAddress, firstPayment);
var handler = tester.PayTester.GetService<BitcoinLikePaymentHandler>();
TestUtils.Eventually(() =>
{
var exportResultPaid = user.GetController<InvoiceController>().Export("csv").GetAwaiter().GetResult();
var paidresult = Assert.IsType<ContentResult>(exportResultPaid);
Assert.Equal("application/csv", paidresult.ContentType);
Assert.Contains($",\"orderId\",\"{invoice.Id}\",", paidresult.Content);
Assert.Contains($",\"OnChain\",\"BTC\",\"0.0991\",\"0.0001\",\"5000.0\"", paidresult.Content);
Assert.Contains($",\"{handler.PrettyDescription}\",\"BTC\",\"0.0991\",\"0.0001\",\"5000.0\"", paidresult.Content);
Assert.Contains($",\"USD\",\"5.00", paidresult.Content); // Seems hacky but some plateform does not render this decimal the same
Assert.Contains($"0\",\"500.0\",\"\",\"Some ``, description\",\"new (paidPartial)\"", paidresult.Content);
});

View File

@ -75,7 +75,7 @@ namespace BTCPayServer.Controllers
StoreId = new[] { this.HttpContext.GetStoreData().Id }
};
var entities = (await _InvoiceRepository.GetInvoices(query))
var entities = (await _InvoiceRepository.GetInvoices(query))
.Select((o) => o.EntityToDTO()).ToArray();
return DataWrapper.Create(entities);

View File

@ -92,7 +92,6 @@ namespace BTCPayServer.Controllers
return View(model);
}
//TODO: abstract
private InvoiceDetailsModel InvoicePopulatePayments(InvoiceEntity invoice)
{
var model = new InvoiceDetailsModel();
@ -107,19 +106,14 @@ namespace BTCPayServer.Controllers
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)
{
cryptoPayment.Address = onchainMethod.DepositAddress;
}
var paymentMethodDetails = data.GetPaymentMethodDetails();
cryptoPayment.Address = paymentMethodDetails.GetPaymentDestination();
cryptoPayment.Rate = ExchangeRate(data);
model.CryptoPayments.Add(cryptoPayment);
}
foreach (var payment in invoice.GetPayments())
{
//TODO: abstract
var paymentNetwork = _NetworkProvider.GetNetwork<BTCPayNetwork>(payment.GetCryptoCode());
if (paymentNetwork == null)
{

View File

@ -28,6 +28,9 @@ namespace BTCPayServer.Data
{
public class StoreData
{
[NotMapped]
[JsonIgnore]
public PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary { get; set; }
public string Id
{
get;
@ -91,7 +94,9 @@ namespace BTCPayServer.Data
continue;
if (strat.Value.Type == JTokenType.Null)
continue;
yield return PaymentMethodExtensions.Deserialize(paymentMethodId, strat.Value, network);
yield return
PaymentMethodHandlerDictionary[paymentMethodId]
.DeserializeSupportedPaymentMethod(paymentMethodId, strat.Value);
}
}
}

View File

@ -181,12 +181,10 @@ namespace BTCPayServer.Hosting
services.AddSingleton<BitcoinLikePaymentHandler>();
services.AddSingleton<IPaymentMethodHandler>(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, BTCPayNetwork>>(provider => provider.GetService<LightningLikePaymentHandler>());
services.AddSingleton<IHostedService, LightningListener>();
services.AddSingleton<PaymentMethodHandlerDictionary>();

View File

@ -106,23 +106,11 @@ namespace BTCPayServer.PaymentRequest
Status = entity.GetInvoiceState().ToString(),
Payments = entity.GetPayments().Select(paymentEntity =>
{
//TODO: abstract
var paymentNetwork = _BtcPayNetworkProvider.GetNetwork<BTCPayNetworkBase>(paymentEntity.GetCryptoCode());
var paymentData = paymentEntity.GetCryptoPaymentData();
string link = null;
string txId = null;
switch (paymentData)
{
case Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData:
txId = onChainPaymentData.Outpoint.Hash.ToString();
link = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink,
txId);
break;
case LightningLikePaymentData lightningLikePaymentData:
txId = lightningLikePaymentData.BOLT11;
break;
}
var paymentMethodId = paymentEntity.GetPaymentMethodId();
string txId = paymentData.GetPaymentId();
string link = paymentEntity.PaymentMethodHandlerDictionary[paymentMethodId].GetTransactionLink(paymentMethodId, txId);
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment()
{
Amount = paymentData.GetValue(),

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Data;
@ -11,6 +12,7 @@ using BTCPayServer.Services.Rates;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments.Bitcoin
{
@ -130,6 +132,13 @@ namespace BTCPayServer.Payments.Bitcoin
}
public override string GetTransactionLink(PaymentMethodId paymentMethodId, params object[] args)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, args);
}
public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store,
BTCPayNetworkBase network)
{
@ -198,5 +207,23 @@ namespace BTCPayServer.Payments.Bitcoin
}
#pragma warning restore 618
}
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(PaymentMethodId paymentMethodId, JToken value)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
if (value is JObject jobj)
{
var scheme = network.NBXplorerNetwork.Serializer.ToObject<DerivationSchemeSettings>(jobj);
scheme.Network = network;
return scheme;
}
// Legacy
return DerivationSchemeSettings.Parse(((JValue)value).Value<string>(), network);
}
public override IPaymentMethodDetails DeserializePaymentMethodDetails(JObject jobj)
{
return JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod>(jobj.ToString());
}
}
}

View File

@ -8,6 +8,7 @@ using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json.Linq;
using InvoiceResponse = BTCPayServer.Models.InvoiceResponse;
namespace BTCPayServer.Payments
@ -17,6 +18,7 @@ namespace BTCPayServer.Payments
/// </summary>
public interface IPaymentMethodHandler
{
string PrettyDescription { get; }
/// <summary>
/// Create needed to track payments of this invoice
/// </summary>
@ -56,6 +58,10 @@ namespace BTCPayServer.Payments
IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
CryptoPaymentData GetCryptoPaymentData(PaymentEntity paymentEntity);
ISupportedPaymentMethod DeserializeSupportedPaymentMethod(PaymentMethodId paymentMethodId, JToken value);
IPaymentMethodDetails DeserializePaymentMethodDetails(JObject jobj);
string GetTransactionLink(PaymentMethodId paymentMethodId, params object[] args);
}
public interface IPaymentMethodHandler<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler
@ -66,8 +72,7 @@ namespace BTCPayServer.Payments
PaymentMethod paymentMethod, StoreData store, TBTCPayNetwork network, object preparePaymentObject);
}
public abstract class
PaymentMethodHandlerBase<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler<
public abstract class PaymentMethodHandlerBase<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler<
TSupportedPaymentMethod, TBTCPayNetwork>
where TSupportedPaymentMethod : ISupportedPaymentMethod
where TBTCPayNetwork : BTCPayNetworkBase
@ -92,6 +97,10 @@ namespace BTCPayServer.Payments
public abstract IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
public abstract CryptoPaymentData GetCryptoPaymentData(PaymentEntity paymentEntity);
public abstract ISupportedPaymentMethod DeserializeSupportedPaymentMethod(PaymentMethodId paymentMethodId, JToken value);
public abstract IPaymentMethodDetails DeserializePaymentMethodDetails(JObject jobj);
public abstract string GetTransactionLink(PaymentMethodId paymentMethodId, params object[] args);
public virtual object PreparePayment(TSupportedPaymentMethod supportedPaymentMethod, StoreData store,
BTCPayNetworkBase network)

View File

@ -1,8 +1,6 @@
using System;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Data;
@ -17,6 +15,7 @@ using BTCPayServer.Services.Rates;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments.Lightning
{
@ -176,6 +175,21 @@ namespace BTCPayServer.Payments.Lightning
#pragma warning restore CS0618
}
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(PaymentMethodId paymentMethodId, JToken value)
{
return JsonConvert.DeserializeObject<LightningSupportedPaymentMethod>(value.ToString());
}
public override IPaymentMethodDetails DeserializePaymentMethodDetails(JObject jobj)
{
return JsonConvert.DeserializeObject<LightningLikePaymentMethodDetails>(jobj.ToString());
}
public override string GetTransactionLink(PaymentMethodId paymentMethodId, params object[] args)
{
return null;
}
public override void PrepareInvoiceDto(InvoiceResponse invoiceResponse, InvoiceEntity invoiceEntity,
InvoiceCryptoInfo invoiceCryptoInfo, PaymentMethodAccounting accounting, PaymentMethod info)
{

View File

@ -1,56 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Payments.Changelly;
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments
{
public class PaymentMethodExtensions
{
public static ISupportedPaymentMethod Deserialize(PaymentMethodId paymentMethodId, JToken value, BTCPayNetworkBase network)
{
//Todo: Abstract
if (paymentMethodId.PaymentType == PaymentTypes.BTCLike)
{
var bitcoinSpecificBtcPayNetwork = (BTCPayNetwork)network;
if (value is JObject jobj)
{
var scheme = bitcoinSpecificBtcPayNetwork.NBXplorerNetwork.Serializer.ToObject<DerivationSchemeSettings>(jobj);
scheme.Network = bitcoinSpecificBtcPayNetwork;
return scheme;
}
// Legacy
else
{
return BTCPayServer.DerivationSchemeSettings.Parse(((JValue)value).Value<string>(), bitcoinSpecificBtcPayNetwork);
}
}
//////////
else if (paymentMethodId.PaymentType == PaymentTypes.LightningLike)
{
return JsonConvert.DeserializeObject<Payments.Lightning.LightningSupportedPaymentMethod>(value.ToString());
}
throw new NotSupportedException();
}
public static IPaymentMethodDetails DeserializePaymentMethodDetails(PaymentMethodId paymentMethodId, JObject jobj)
{
//Todo: Abstract
if(paymentMethodId.PaymentType == PaymentTypes.BTCLike)
{
return JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod>(jobj.ToString());
}
if (paymentMethodId.PaymentType == PaymentTypes.LightningLike)
{
return JsonConvert.DeserializeObject<Payments.Lightning.LightningLikePaymentMethodDetails>(jobj.ToString());
}
throw new NotSupportedException(paymentMethodId.PaymentType.ToString());
}
public static JToken Serialize(ISupportedPaymentMethod factory)
{
// Legacy
@ -66,8 +20,6 @@ namespace BTCPayServer.Payments
var str = JsonConvert.SerializeObject(factory);
return JObject.Parse(str);
}
throw new NotSupportedException();
}
}
}

View File

@ -16,6 +16,7 @@ using BTCPayServer.Security;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Ganss.XSS;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http.Extensions;
@ -34,6 +35,7 @@ namespace BTCPayServer.Services.Apps
ApplicationDbContextFactory _ContextFactory;
private readonly InvoiceRepository _InvoiceRepository;
CurrencyNameTable _Currencies;
private readonly StoreRepository _storeRepository;
private readonly HtmlSanitizer _HtmlSanitizer;
private readonly BTCPayNetworkProvider _Networks;
public CurrencyNameTable Currencies => _Currencies;
@ -41,11 +43,13 @@ namespace BTCPayServer.Services.Apps
InvoiceRepository invoiceRepository,
BTCPayNetworkProvider networks,
CurrencyNameTable currencies,
StoreRepository storeRepository,
HtmlSanitizer htmlSanitizer)
{
_ContextFactory = contextFactory;
_InvoiceRepository = invoiceRepository;
_Currencies = currencies;
_storeRepository = storeRepository;
_HtmlSanitizer = htmlSanitizer;
_Networks = networks;
}
@ -247,12 +251,9 @@ namespace BTCPayServer.Services.Apps
}
}
public async Task<StoreData> GetStore(AppData app)
public Task<StoreData> GetStore(AppData app)
{
using (var ctx = _ContextFactory.CreateContext())
{
return await ctx.Stores.FirstOrDefaultAsync(s => s.Id == app.StoreDataId);
}
return _storeRepository.FindStore(app.StoreDataId);
}

View File

@ -78,8 +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",
PaymentType = invoice.PaymentMethodHandlerDictionary[payment.GetPaymentMethodId()].PrettyDescription,
Destination = payment.GetCryptoPaymentData().GetDestination(Networks.GetNetwork<BTCPayNetworkBase>(cryptoCode)),
Paid = pdata.GetValue().ToString(CultureInfo.InvariantCulture),
PaidCurrency = Math.Round(pdata.GetValue() * pmethod.Rate, currency.NumberDecimalDigits).ToString(CultureInfo.InvariantCulture),

View File

@ -222,7 +222,8 @@ namespace BTCPayServer.Services.Invoices
{
if (network == Networks.BTC && paymentMethodId.PaymentType == PaymentTypes.BTCLike)
btcReturned = true;
yield return PaymentMethodExtensions.Deserialize(paymentMethodId, strat.Value, network);
yield return PaymentMethodHandlerDictionary[paymentMethodId]
.DeserializeSupportedPaymentMethod(paymentMethodId, strat.Value);
}
}
}
@ -755,7 +756,8 @@ namespace BTCPayServer.Services.Invoices
}
else
{
var details = PaymentMethodExtensions.DeserializePaymentMethodDetails(GetId(), PaymentMethodDetails);
var details = ParentEntity.PaymentMethodHandlerDictionary[GetId()]
.DeserializePaymentMethodDetails(PaymentMethodDetails);
if (details is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod btcLike)
{
btcLike.NextNetworkFee = NextNetworkFee;

View File

@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using NBitcoin;
@ -17,11 +18,14 @@ namespace BTCPayServer.Services.PaymentRequests
{
private readonly ApplicationDbContextFactory _ContextFactory;
private readonly InvoiceRepository _InvoiceRepository;
private readonly StoreRepository _storeRepository;
public PaymentRequestRepository(ApplicationDbContextFactory contextFactory, InvoiceRepository invoiceRepository)
public PaymentRequestRepository(ApplicationDbContextFactory contextFactory, InvoiceRepository invoiceRepository,
StoreRepository storeRepository)
{
_ContextFactory = contextFactory;
_InvoiceRepository = invoiceRepository;
_storeRepository = storeRepository;
}
@ -52,11 +56,17 @@ namespace BTCPayServer.Services.PaymentRequests
using (var context = _ContextFactory.CreateContext())
{
return await context.PaymentRequests.Include(x => x.StoreData)
var result = await context.PaymentRequests.Include(x => x.StoreData)
.Where(data =>
string.IsNullOrEmpty(userId) ||
(data.StoreData != null && data.StoreData.UserStores.Any(u => u.ApplicationUserId == userId)))
.SingleOrDefaultAsync(x => x.Id == id, cancellationToken);
if (result != null)
{
result.StoreData = _storeRepository.PrepareEntity(result.StoreData);
}
return result;
}
}

View File

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Invoices;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Services.Stores
@ -13,9 +14,12 @@ namespace BTCPayServer.Services.Stores
public class StoreRepository
{
private ApplicationDbContextFactory _ContextFactory;
public StoreRepository(ApplicationDbContextFactory contextFactory)
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
public StoreRepository(ApplicationDbContextFactory contextFactory, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
{
_ContextFactory = contextFactory ?? throw new ArgumentNullException(nameof(contextFactory));
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
}
public async Task<StoreData> FindStore(string storeId)
@ -24,7 +28,8 @@ namespace BTCPayServer.Services.Stores
return null;
using (var ctx = _ContextFactory.CreateContext())
{
return await ctx.FindAsync<StoreData>(storeId).ConfigureAwait(false);
var result = await ctx.FindAsync<StoreData>(storeId).ConfigureAwait(false);
return PrepareEntity(result);
}
}
@ -47,7 +52,7 @@ namespace BTCPayServer.Services.Stores
#pragma warning disable CS0612 // Type or member is obsolete
us.Store.Role = us.Role;
#pragma warning restore CS0612 // Type or member is obsolete
return us.Store;
return PrepareEntity(us.Store);
}).FirstOrDefault();
}
}
@ -89,7 +94,7 @@ namespace BTCPayServer.Services.Stores
#pragma warning disable CS0612 // Type or member is obsolete
u.StoreData.Role = u.Role;
#pragma warning restore CS0612 // Type or member is obsolete
return u.StoreData;
return PrepareEntity(u.StoreData);
}).ToArray();
}
}
@ -170,7 +175,7 @@ namespace BTCPayServer.Services.Stores
{
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)),
StoreName = name,
SpeedPolicy = Invoices.SpeedPolicy.MediumSpeed
SpeedPolicy = SpeedPolicy.MediumSpeed
};
var userStore = new UserStore
{
@ -181,7 +186,7 @@ namespace BTCPayServer.Services.Stores
ctx.Add(store);
ctx.Add(userStore);
await ctx.SaveChangesAsync().ConfigureAwait(false);
return store;
return PrepareEntity(store);
}
}
@ -230,5 +235,12 @@ namespace BTCPayServer.Services.Stores
return ctx.Database.SupportDropForeignKey();
}
}
public StoreData PrepareEntity(StoreData storeData)
{
if(storeData != null)
storeData.PaymentMethodHandlerDictionary = _paymentMethodHandlerDictionary;
return storeData;
}
}
}

View File

@ -22,7 +22,9 @@
{
<tr>
<td>@payment.PaymentMethod</td>
<td>@payment.Address</td>
<td title="@payment.Address">
<span class="text-truncate d-block" style="max-width: 400px">@payment.Address</span>
</td>
<td class="text-right">@payment.Rate</td>
<td class="text-right">@payment.Paid</td>
<td class="text-right">@payment.Due</td>