mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 18:11:36 +01:00
2317a7df55
fixes #3966
134 lines
6.1 KiB
C#
134 lines
6.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using BTCPayServer.Client.Models;
|
|
using BTCPayServer.Data;
|
|
using BTCPayServer.Events;
|
|
using BTCPayServer.Payments;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using NBitcoin;
|
|
|
|
namespace BTCPayServer.Services.Invoices
|
|
{
|
|
public class PaymentService
|
|
{
|
|
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
|
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
private readonly InvoiceRepository _invoiceRepository;
|
|
private readonly EventAggregator _eventAggregator;
|
|
|
|
public PaymentService(EventAggregator eventAggregator,
|
|
ApplicationDbContextFactory applicationDbContextFactory,
|
|
BTCPayNetworkProvider btcPayNetworkProvider, InvoiceRepository invoiceRepository)
|
|
{
|
|
_applicationDbContextFactory = applicationDbContextFactory;
|
|
_btcPayNetworkProvider = btcPayNetworkProvider;
|
|
_invoiceRepository = invoiceRepository;
|
|
_eventAggregator = eventAggregator;
|
|
}
|
|
/// <summary>
|
|
/// Add a payment to an invoice
|
|
/// </summary>
|
|
/// <param name="invoiceId"></param>
|
|
/// <param name="date"></param>
|
|
/// <param name="paymentData"></param>
|
|
/// <param name="cryptoCode"></param>
|
|
/// <param name="accounted"></param>
|
|
/// <returns>The PaymentEntity or null if already added</returns>
|
|
public async Task<PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, BTCPayNetworkBase network, bool accounted = false)
|
|
{
|
|
await using var context = _applicationDbContextFactory.CreateContext();
|
|
var invoice = await context.Invoices.FindAsync(invoiceId);
|
|
if (invoice == null)
|
|
return null;
|
|
InvoiceEntity invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
|
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()));
|
|
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
|
PaymentEntity entity = new PaymentEntity
|
|
{
|
|
Version = 1,
|
|
#pragma warning disable CS0618
|
|
CryptoCode = network.CryptoCode,
|
|
#pragma warning restore CS0618
|
|
ReceivedTime = date.UtcDateTime,
|
|
Accounted = accounted,
|
|
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
|
|
Network = network
|
|
};
|
|
entity.SetCryptoPaymentData(paymentData);
|
|
//TODO: abstract
|
|
if (paymentMethodDetails is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod &&
|
|
bitcoinPaymentMethod.NetworkFeeMode == NetworkFeeMode.MultiplePaymentsOnly &&
|
|
bitcoinPaymentMethod.NextNetworkFee == Money.Zero)
|
|
{
|
|
bitcoinPaymentMethod.NextNetworkFee = bitcoinPaymentMethod.NetworkFeeRate.GetFee(100); // assume price for 100 bytes
|
|
paymentMethod.SetPaymentMethodDetails(bitcoinPaymentMethod);
|
|
invoiceEntity.SetPaymentMethod(paymentMethod);
|
|
invoice.Blob = InvoiceRepository.ToBytes(invoiceEntity, network);
|
|
}
|
|
PaymentData data = new PaymentData
|
|
{
|
|
Id = paymentData.GetPaymentId(),
|
|
Blob = InvoiceRepository.ToBytes(entity, entity.Network),
|
|
InvoiceDataId = invoiceId,
|
|
Accounted = accounted
|
|
};
|
|
|
|
await context.Payments.AddAsync(data);
|
|
|
|
InvoiceRepository.AddToTextSearch(context, invoice, paymentData.GetSearchTerms());
|
|
var alreadyExists = false;
|
|
try
|
|
{
|
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
}
|
|
catch (DbUpdateException) { alreadyExists = true; }
|
|
|
|
if (alreadyExists)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (paymentData.PaymentConfirmed(entity, invoiceEntity.SpeedPolicy))
|
|
{
|
|
_eventAggregator.Publish(new InvoiceEvent(invoiceEntity, InvoiceEvent.PaymentSettled) { Payment = entity });
|
|
}
|
|
return entity;
|
|
}
|
|
|
|
public async Task UpdatePayments(List<PaymentEntity> payments)
|
|
{
|
|
if (payments.Count == 0)
|
|
return;
|
|
await using var context = _applicationDbContextFactory.CreateContext();
|
|
var paymentsDict = payments
|
|
.Select(entity => (entity, entity.GetCryptoPaymentData()))
|
|
.ToDictionary(tuple => tuple.Item2.GetPaymentId());
|
|
var paymentIds = paymentsDict.Keys.ToArray();
|
|
var dbPayments = await context.Payments
|
|
.Include(data => data.InvoiceData)
|
|
.Where(data => paymentIds.Contains(data.Id)).ToDictionaryAsync(data => data.Id);
|
|
var eventsToSend = new List<InvoiceEvent>();
|
|
foreach (KeyValuePair<string, (PaymentEntity entity, CryptoPaymentData)> payment in paymentsDict)
|
|
{
|
|
var dbPayment = dbPayments[payment.Key];
|
|
var invBlob = _invoiceRepository.ToEntity(dbPayment.InvoiceData);
|
|
var dbPaymentEntity = dbPayment.GetBlob(_btcPayNetworkProvider);
|
|
var wasConfirmed = dbPayment.GetBlob(_btcPayNetworkProvider).GetCryptoPaymentData()
|
|
.PaymentConfirmed(dbPaymentEntity, invBlob.SpeedPolicy);
|
|
if (!wasConfirmed && payment.Value.Item2.PaymentConfirmed(payment.Value.entity, invBlob.SpeedPolicy))
|
|
{
|
|
eventsToSend.Add(new InvoiceEvent(invBlob, InvoiceEvent.PaymentSettled) { Payment = payment.Value.entity });
|
|
}
|
|
|
|
dbPayment.Accounted = payment.Value.entity.Accounted;
|
|
dbPayment.Blob = InvoiceRepository.ToBytes(payment.Value.entity, payment.Value.entity.Network);
|
|
}
|
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
eventsToSend.ForEach(_eventAggregator.Publish);
|
|
}
|
|
|
|
}
|
|
}
|