The Big Cleanup: Refactor BTCPay internals (#5809)

This commit is contained in:
Nicolas Dorier
2024-04-04 16:31:04 +09:00
committed by GitHub
parent 69b589a401
commit 6cc1751924
265 changed files with 8289 additions and 7673 deletions

View File

@@ -11,6 +11,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Rating;
using BTCPayServer.Security.Greenfield;
using BTCPayServer.Services;
@@ -21,7 +22,9 @@ using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NBitcoin;
using Newtonsoft.Json.Linq;
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
@@ -42,6 +45,8 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly InvoiceActivator _invoiceActivator;
private readonly ApplicationDbContextFactory _dbContextFactory;
private readonly IAuthorizationService _authorizationService;
private readonly Dictionary<PaymentMethodId, IPaymentLinkExtension> _paymentLinkExtensions;
private readonly PaymentMethodHandlerDictionary _handlers;
public LanguageService LanguageService { get; }
@@ -51,7 +56,9 @@ namespace BTCPayServer.Controllers.Greenfield
InvoiceActivator invoiceActivator,
PullPaymentHostedService pullPaymentService,
ApplicationDbContextFactory dbContextFactory,
IAuthorizationService authorizationService)
IAuthorizationService authorizationService,
Dictionary<PaymentMethodId, IPaymentLinkExtension> paymentLinkExtensions,
PaymentMethodHandlerDictionary handlers)
{
_invoiceController = invoiceController;
_invoiceRepository = invoiceRepository;
@@ -63,6 +70,8 @@ namespace BTCPayServer.Controllers.Greenfield
_pullPaymentService = pullPaymentService;
_dbContextFactory = dbContextFactory;
_authorizationService = authorizationService;
_paymentLinkExtensions = paymentLinkExtensions;
_handlers = handlers;
LanguageService = languageService;
}
@@ -347,7 +356,7 @@ namespace BTCPayServer.Controllers.Greenfield
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
{
await _invoiceActivator.ActivateInvoicePaymentMethod(paymentMethodId, invoice, store);
await _invoiceActivator.ActivateInvoicePaymentMethod(invoiceId, paymentMethodId);
return Ok();
}
ModelState.AddModelError(nameof(paymentMethod), "Invalid payment method");
@@ -384,33 +393,31 @@ namespace BTCPayServer.Controllers.Greenfield
{
return this.CreateAPIError("non-refundable", "Cannot refund this invoice");
}
PaymentMethod? invoicePaymentMethod = null;
PaymentPrompt? paymentPrompt = null;
PaymentMethodId? paymentMethodId = null;
if (request.PaymentMethod is not null && PaymentMethodId.TryParse(request.PaymentMethod, out paymentMethodId))
{
invoicePaymentMethod = invoice.GetPaymentMethods().SingleOrDefault(method => method.GetId() == paymentMethodId);
paymentPrompt = invoice.GetPaymentPrompt(paymentMethodId);
}
if (invoicePaymentMethod is null)
if (paymentPrompt is null)
{
ModelState.AddModelError(nameof(request.PaymentMethod), "Please select one of the payment methods which were available for the original invoice");
}
if (request.RefundVariant is null)
ModelState.AddModelError(nameof(request.RefundVariant), "`refundVariant` is mandatory");
if (!ModelState.IsValid || invoicePaymentMethod is null || paymentMethodId is null)
if (!ModelState.IsValid || paymentPrompt is null || paymentMethodId is null)
return this.CreateValidationError(ModelState);
var accounting = invoicePaymentMethod.Calculate();
var accounting = paymentPrompt.Calculate();
var cryptoPaid = accounting.Paid;
var cdCurrency = _currencyNameTable.GetCurrencyData(invoice.Currency, true);
var paidCurrency = Math.Round(cryptoPaid * invoicePaymentMethod.Rate, cdCurrency.Divisibility);
var paidCurrency = Math.Round(cryptoPaid * paymentPrompt.Rate, cdCurrency.Divisibility);
var rateResult = await _rateProvider.FetchRate(
new CurrencyPair(paymentMethodId.CryptoCode, invoice.Currency),
new CurrencyPair(paymentPrompt.Currency, invoice.Currency),
store.GetStoreBlob().GetRateRules(_networkProvider),
cancellationToken
);
var cryptoCode = invoicePaymentMethod.GetId().CryptoCode;
var paymentMethodDivisibility = _currencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
var paidAmount = cryptoPaid.RoundToSignificant(paymentMethodDivisibility);
var paidAmount = cryptoPaid.RoundToSignificant(paymentPrompt.Divisibility);
var createPullPayment = new CreatePullPayment
{
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration,
@@ -436,17 +443,17 @@ namespace BTCPayServer.Controllers.Greenfield
return this.CreateValidationError(ModelState);
}
var appliedDivisibility = paymentMethodDivisibility;
var appliedDivisibility = paymentPrompt.Divisibility;
switch (request.RefundVariant)
{
case RefundVariant.RateThen:
createPullPayment.Currency = cryptoCode;
createPullPayment.Currency = paymentPrompt.Currency;
createPullPayment.Amount = paidAmount;
createPullPayment.AutoApproveClaims = true;
break;
case RefundVariant.CurrentRate:
createPullPayment.Currency = cryptoCode;
createPullPayment.Currency = paymentPrompt.Currency;
createPullPayment.Amount = Math.Round(paidCurrency / rateResult.BidAsk.Bid, appliedDivisibility);
createPullPayment.AutoApproveClaims = true;
break;
@@ -469,7 +476,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
var dueAmount = accounting.TotalDue;
createPullPayment.Currency = cryptoCode;
createPullPayment.Currency = paymentPrompt.Currency;
createPullPayment.Amount = Math.Round(paidAmount - dueAmount, appliedDivisibility);
createPullPayment.AutoApproveClaims = true;
break;
@@ -501,7 +508,7 @@ namespace BTCPayServer.Controllers.Greenfield
createPullPayment.Currency = request.CustomCurrency;
createPullPayment.Amount = request.CustomAmount.Value;
createPullPayment.AutoApproveClaims = paymentMethodId.CryptoCode == request.CustomCurrency;
createPullPayment.AutoApproveClaims = paymentPrompt.Currency == request.CustomCurrency;
break;
default:
@@ -569,49 +576,52 @@ namespace BTCPayServer.Controllers.Greenfield
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity, bool includeAccountedPaymentOnly)
{
return entity.GetPaymentMethods().Select(
method =>
return entity.GetPaymentPrompts().Select(
prompt =>
{
var accounting = method.Calculate();
var details = method.GetPaymentMethodDetails();
var payments = method.ParentEntity.GetPayments(includeAccountedPaymentOnly).Where(paymentEntity =>
paymentEntity.GetPaymentMethodId() == method.GetId());
_handlers.TryGetValue(prompt.PaymentMethodId, out var handler);
var accounting = prompt.Currency is not null ? prompt.Calculate() : null;
var payments = prompt.ParentEntity.GetPayments(includeAccountedPaymentOnly).Where(paymentEntity =>
paymentEntity.PaymentMethodId == prompt.PaymentMethodId);
_paymentLinkExtensions.TryGetValue(prompt.PaymentMethodId, out var paymentLinkExtension);
var details = prompt.Details;
if (handler is not null && prompt.Activated)
details = JToken.FromObject(handler.ParsePaymentPromptDetails(details), handler.Serializer.ForAPI());
return new InvoicePaymentMethodDataModel
{
Activated = details.Activated,
PaymentMethod = method.GetId().ToStringNormalized(),
CryptoCode = method.GetId().CryptoCode,
Destination = details.GetPaymentDestination(),
Rate = method.Rate,
Due = accounting.DueUncapped,
TotalPaid = accounting.Paid,
PaymentMethodPaid = accounting.CryptoPaid,
Amount = accounting.TotalDue,
NetworkFee = accounting.NetworkFee,
PaymentLink =
method.GetId().PaymentType.GetPaymentLink(method.Network, entity, details, accounting.Due,
Request.GetAbsoluteRoot()),
Activated = prompt.Activated,
PaymentMethodId = prompt.PaymentMethodId.ToString(),
Currency = prompt.Currency,
Destination = prompt.Destination,
Rate = prompt.Currency is not null ? prompt.Rate : 0m,
Due = accounting?.DueUncapped ?? 0m,
TotalPaid = accounting?.Paid ?? 0m,
PaymentMethodPaid = accounting?.PaymentMethodPaid ?? 0m,
Amount = accounting?.TotalDue ?? 0m,
PaymentMethodFee = accounting?.PaymentMethodFee ?? 0m,
PaymentLink = (prompt.Activated ? paymentLinkExtension?.GetPaymentLink(prompt, Url) : null) ?? string.Empty,
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
AdditionalData = details.GetAdditionalData()
AdditionalData = prompt.Details
};
}).ToArray();
}
public static InvoicePaymentMethodDataModel.Payment ToPaymentModel(InvoiceEntity entity, PaymentEntity paymentEntity)
{
var data = paymentEntity.GetCryptoPaymentData();
return new InvoicePaymentMethodDataModel.Payment()
{
Destination = data.GetDestination(),
Id = data.GetPaymentId(),
Status = !paymentEntity.Accounted
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid
: data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) || data.PaymentCompleted(paymentEntity)
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
Fee = paymentEntity.NetworkFee,
Value = data.GetValue(),
Destination = paymentEntity.Destination,
Id = paymentEntity.Id,
Status = paymentEntity.Status switch
{
PaymentStatus.Processing => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
PaymentStatus.Settled => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled,
PaymentStatus.Unaccounted => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid,
_ => throw new NotSupportedException(paymentEntity.Status.ToString())
},
Fee = paymentEntity.PaymentMethodFee,
Value = paymentEntity.Value,
ReceivedDate = paymentEntity.ReceivedTime.DateTime
};
}
@@ -656,8 +666,8 @@ namespace BTCPayServer.Controllers.Greenfield
Monitoring = entity.MonitoringExpiration - entity.ExpirationTime,
PaymentTolerance = entity.PaymentTolerance,
PaymentMethods =
entity.GetPaymentMethods().Select(method => method.GetId().ToStringNormalized()).ToArray(),
DefaultPaymentMethod = entity.DefaultPaymentMethod,
entity.GetPaymentPrompts().Select(method => method.PaymentMethodId.ToString()).ToArray(),
DefaultPaymentMethod = entity.DefaultPaymentMethod?.ToString(),
SpeedPolicy = entity.SpeedPolicy,
DefaultLanguage = entity.DefaultLanguage,
RedirectAutomatically = entity.RedirectAutomatically,