btcpayserver/BTCPayServer/Services/Invoices/InvoiceRepository.cs

837 lines
34 KiB
C#
Raw Normal View History

2017-09-13 15:47:34 +09:00
using System;
using System.Collections.Generic;
2020-06-28 17:55:27 +09:00
using System.Globalization;
2017-09-13 15:47:34 +09:00
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Data;
2020-07-24 09:40:37 +02:00
using BTCPayServer.Events;
2018-01-18 18:33:26 +09:00
using BTCPayServer.Logging;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
2020-06-28 17:55:27 +09:00
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
2019-12-24 08:20:44 +01:00
using Encoders = NBitcoin.DataEncoders.Encoders;
2020-07-22 13:58:41 +02:00
using InvoiceData = BTCPayServer.Data.InvoiceData;
2017-09-13 15:47:34 +09:00
2017-10-20 14:06:37 -05:00
namespace BTCPayServer.Services.Invoices
2017-09-13 15:47:34 +09:00
{
public class InvoiceRepository
{
static JsonSerializerSettings DefaultSerializerSettings;
static InvoiceRepository()
{
DefaultSerializerSettings = new JsonSerializerSettings();
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
}
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
2020-07-24 09:40:37 +02:00
private readonly EventAggregator _eventAggregator;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
public InvoiceRepository(ApplicationDbContextFactory contextFactory,
2020-07-24 09:40:37 +02:00
BTCPayNetworkProvider networks, EventAggregator eventAggregator)
{
_applicationDbContextFactory = contextFactory;
_btcPayNetworkProvider = networks;
2020-07-24 09:40:37 +02:00
_eventAggregator = eventAggregator;
}
2020-11-13 14:01:51 +09:00
public async Task<Data.WebhookDeliveryData> GetWebhookDelivery(string invoiceId, string deliveryId)
2020-11-06 20:42:26 +09:00
{
using var ctx = _applicationDbContextFactory.CreateContext();
2020-11-06 20:42:26 +09:00
return await ctx.InvoiceWebhookDeliveries
.Where(d => d.InvoiceId == invoiceId && d.DeliveryId == deliveryId)
.Select(d => d.Delivery)
.FirstOrDefaultAsync();
}
public InvoiceEntity CreateNewInvoice()
{
return new InvoiceEntity()
{
Networks = _btcPayNetworkProvider,
Version = InvoiceEntity.Lastest_Version,
InvoiceTime = DateTimeOffset.UtcNow,
Metadata = new InvoiceMetadata()
};
}
public async Task<bool> RemovePendingInvoice(string invoiceId)
{
2018-01-18 18:33:26 +09:00
Logs.PayServer.LogInformation($"Remove pending invoice {invoiceId}");
using (var ctx = _applicationDbContextFactory.CreateContext())
{
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
try
{
await ctx.SaveChangesAsync();
return true;
}
catch (DbUpdateException) { return false; }
}
}
public async Task<IEnumerable<InvoiceEntity>> GetInvoicesFromAddresses(string[] addresses)
{
using (var db = _applicationDbContextFactory.CreateContext())
{
2020-06-28 17:55:27 +09:00
return (await db.AddressInvoices
.Include(a => a.InvoiceData.Payments)
#pragma warning disable CS0618
.Where(a => addresses.Contains(a.Address))
#pragma warning restore CS0618
.Select(a => a.InvoiceData)
.ToListAsync()).Select(ToEntity);
}
}
2020-02-19 21:43:10 +01:00
public async Task<string[]> GetPendingInvoices()
{
using (var ctx = _applicationDbContextFactory.CreateContext())
{
2020-02-19 21:43:10 +01:00
return await ctx.PendingInvoices.AsQueryable().Select(data => data.Id).ToArrayAsync();
}
}
2020-11-13 14:01:51 +09:00
public async Task<List<Data.WebhookDeliveryData>> GetWebhookDeliveries(string invoiceId)
2020-11-06 20:42:26 +09:00
{
using var ctx = _applicationDbContextFactory.CreateContext();
2020-11-06 20:42:26 +09:00
return await ctx.InvoiceWebhookDeliveries
.Where(s => s.InvoiceId == invoiceId)
.Select(s => s.Delivery)
.OrderByDescending(s => s.Timestamp)
.ToListAsync();
}
public async Task<AppData[]> GetAppsTaggingStore(string storeId)
{
if (storeId == null)
throw new ArgumentNullException(nameof(storeId));
using (var ctx = _applicationDbContextFactory.CreateContext())
{
return await ctx.Apps.Where(a => a.StoreDataId == storeId && a.TagAllInvoices).ToArrayAsync();
}
}
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
{
using (var ctx = _applicationDbContextFactory.CreateContext())
{
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false);
if (invoiceData == null)
return;
if (invoiceData.CustomerEmail == null && data.Email != null)
{
invoiceData.CustomerEmail = data.Email;
AddToTextSearch(ctx, invoiceData, invoiceData.CustomerEmail);
}
await ctx.SaveChangesAsync().ConfigureAwait(false);
}
}
public async Task ExtendInvoiceMonitor(string invoiceId)
{
using (var ctx = _applicationDbContextFactory.CreateContext())
{
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
var invoice = invoiceData.GetBlob(_btcPayNetworkProvider);
invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1);
invoiceData.Blob = ToBytes(invoice, null);
await ctx.SaveChangesAsync();
}
}
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, string[] additionalSearchTerms = null)
{
var textSearch = new HashSet<string>();
2020-06-24 17:51:00 +09:00
invoice = Clone(invoice);
invoice.Networks = _btcPayNetworkProvider;
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
2018-01-10 18:30:45 +09:00
#pragma warning disable CS0618
invoice.Payments = new List<PaymentEntity>();
2018-01-10 18:30:45 +09:00
#pragma warning restore CS0618
invoice.StoreId = storeId;
using (var context = _applicationDbContextFactory.CreateContext())
{
var invoiceData = new Data.InvoiceData()
{
StoreDataId = storeId,
Id = invoice.Id,
Created = invoice.InvoiceTime,
Blob = ToBytes(invoice, null),
OrderId = invoice.Metadata.OrderId,
#pragma warning disable CS0618 // Type or member is obsolete
Status = invoice.StatusString,
#pragma warning restore CS0618 // Type or member is obsolete
ItemCode = invoice.Metadata.ItemCode,
2020-05-07 12:50:07 +02:00
CustomerEmail = invoice.RefundMail,
Archived = false
};
await context.Invoices.AddAsync(invoiceData);
foreach (var paymentMethod in invoice.GetPaymentMethods())
{
if (paymentMethod.Network == null)
throw new InvalidOperationException("CryptoCode unsupported");
var details = paymentMethod.GetPaymentMethodDetails();
if (!details.Activated)
{
continue;
}
var paymentDestination = details.GetPaymentDestination();
string address = GetDestination(paymentMethod);
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
{
InvoiceDataId = invoice.Id,
CreatedTime = DateTimeOffset.UtcNow,
}.Set(address, paymentMethod.GetId()));
await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
{
InvoiceDataId = invoice.Id,
Assigned = DateTimeOffset.UtcNow
}.SetAddress(paymentDestination, paymentMethod.GetId().ToString()));
textSearch.Add(paymentDestination);
textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
}
await context.PendingInvoices.AddAsync(new PendingInvoiceData() { Id = invoice.Id });
textSearch.Add(invoice.Id);
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
2021-08-03 17:03:00 +09:00
if (!invoice.IsUnsetTopUp())
textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture));
textSearch.Add(invoice.Metadata.OrderId);
textSearch.Add(invoice.StoreId);
textSearch.Add(invoice.Metadata.BuyerEmail);
if (additionalSearchTerms != null)
{
textSearch.AddRange(additionalSearchTerms);
}
AddToTextSearch(context, invoiceData, textSearch.ToArray());
await context.SaveChangesAsync().ConfigureAwait(false);
}
return invoice;
}
2020-06-24 17:51:00 +09:00
private InvoiceEntity Clone(InvoiceEntity invoice)
{
var temp = new InvoiceData();
temp.Blob = ToBytes(invoice);
return temp.GetBlob(_btcPayNetworkProvider);
2020-06-24 17:51:00 +09:00
}
public async Task AddInvoiceLogs(string invoiceId, InvoiceLogs logs)
{
await using var context = _applicationDbContextFactory.CreateContext();
foreach (var log in logs.ToList())
{
await context.InvoiceEvents.AddAsync(new InvoiceEventData()
{
Severity = log.Severity,
InvoiceDataId = invoiceId,
Message = log.Log,
Timestamp = log.Timestamp,
UniqueId = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10))
});
}
await context.SaveChangesAsync().ConfigureAwait(false);
}
private string GetDestination(PaymentMethod paymentMethod)
{
2018-02-19 11:31:34 +09:00
// For legacy reason, BitcoinLikeOnChain is putting the hashes of addresses in database
if (paymentMethod.GetId().PaymentType == Payments.PaymentTypes.BTCLike)
{
var network = (BTCPayNetwork)paymentMethod.Network;
var details =
(Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod)paymentMethod.GetPaymentMethodDetails();
if (!details.Activated)
{
return null;
}
return details.GetDepositAddress(network.NBitcoinNetwork).ScriptPubKey.Hash.ToString();
}
2018-02-19 11:31:34 +09:00
///////////////
return paymentMethod.GetPaymentMethodDetails().GetPaymentDestination();
}
public async Task<bool> NewPaymentDetails(string invoiceId, IPaymentMethodDetails paymentMethodDetails, BTCPayNetworkBase network)
{
await using var context = _applicationDbContextFactory.CreateContext();
var invoice = (await context.Invoices.Where(i => i.Id == invoiceId).ToListAsync()).FirstOrDefault();
if (invoice == null)
return false;
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
var paymentMethod = invoiceEntity.GetPaymentMethod(network, paymentMethodDetails.GetPaymentType());
if (paymentMethod == null)
return false;
var existingPaymentMethod = paymentMethod.GetPaymentMethodDetails();
if (existingPaymentMethod.GetPaymentDestination() != null)
{
MarkUnassigned(invoiceId, context, paymentMethod.GetId());
}
paymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
#pragma warning disable CS0618
if (network.IsBTC)
{
invoiceEntity.DepositAddress = paymentMethod.DepositAddress;
}
#pragma warning restore CS0618
invoiceEntity.SetPaymentMethod(paymentMethod);
invoice.Blob = ToBytes(invoiceEntity, network);
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
{
InvoiceDataId = invoiceId,
CreatedTime = DateTimeOffset.UtcNow
}
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
2020-01-06 13:57:32 +01:00
{
InvoiceDataId = invoiceId,
Assigned = DateTimeOffset.UtcNow
}.SetAddress(paymentMethodDetails.GetPaymentDestination(), network.CryptoCode));
AddToTextSearch(context, invoice, paymentMethodDetails.GetPaymentDestination());
await context.SaveChangesAsync();
return true;
2020-01-06 13:57:32 +01:00
}
public async Task UpdateInvoicePaymentMethod(string invoiceId, PaymentMethod paymentMethod)
{
using (var context = _applicationDbContextFactory.CreateContext())
{
var invoice = await context.Invoices.FindAsync(invoiceId);
if (invoice == null)
return;
var network = paymentMethod.Network;
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
var newDetails = paymentMethod.GetPaymentMethodDetails();
var existing = invoiceEntity.GetPaymentMethod(paymentMethod.GetId());
if (existing.GetPaymentMethodDetails().GetPaymentDestination() != newDetails.GetPaymentDestination() && newDetails.Activated)
{
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
{
InvoiceDataId = invoiceId,
CreatedTime = DateTimeOffset.UtcNow
}
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
{
InvoiceDataId = invoiceId,
Assigned = DateTimeOffset.UtcNow
}.SetAddress(paymentMethod.GetPaymentMethodDetails().GetPaymentDestination(), network.CryptoCode));
}
invoiceEntity.SetPaymentMethod(paymentMethod);
invoice.Blob = ToBytes(invoiceEntity, network);
AddToTextSearch(context, invoice, paymentMethod.GetPaymentMethodDetails().GetPaymentDestination());
await context.SaveChangesAsync();
}
}
public async Task AddPendingInvoiceIfNotPresent(string invoiceId)
{
using (var context = _applicationDbContextFactory.CreateContext())
{
if (!context.PendingInvoices.Any(a => a.Id == invoiceId))
{
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoiceId });
2020-03-30 00:28:22 +09:00
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateException) { } // Already exists
}
}
}
public async Task AddInvoiceEvent(string invoiceId, object evt, InvoiceEventData.EventSeverity severity)
2018-01-14 21:48:23 +09:00
{
await using var context = _applicationDbContextFactory.CreateContext();
await context.InvoiceEvents.AddAsync(new InvoiceEventData()
2018-01-14 21:48:23 +09:00
{
Severity = severity,
InvoiceDataId = invoiceId,
Message = evt.ToString(),
Timestamp = DateTimeOffset.UtcNow,
UniqueId = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10))
});
try
{
await context.SaveChangesAsync();
2018-01-14 21:48:23 +09:00
}
catch (DbUpdateException) { } // Probably the invoice does not exists anymore
2018-01-14 21:48:23 +09:00
}
private static void MarkUnassigned(string invoiceId, ApplicationDbContext context,
PaymentMethodId paymentMethodId)
{
var paymentMethodIdStr = paymentMethodId?.ToString();
var addresses = context.HistoricalAddressInvoices.Where(data =>
(data.InvoiceDataId == invoiceId && paymentMethodIdStr == null ||
2020-10-26 14:18:38 +09:00
#pragma warning disable CS0618 // Type or member is obsolete
data.CryptoCode == paymentMethodIdStr) &&
2020-10-26 14:18:38 +09:00
#pragma warning restore CS0618 // Type or member is obsolete
data.UnAssigned == null);
foreach (var historicalAddressInvoiceData in addresses)
{
historicalAddressInvoiceData.UnAssigned = DateTimeOffset.UtcNow;
}
}
public async Task UnaffectAddress(string invoiceId)
{
await using var context = _applicationDbContextFactory.CreateContext();
MarkUnassigned(invoiceId, context, null);
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateException) { } //Possibly, it was unassigned before
}
public static void AddToTextSearch(ApplicationDbContext context, InvoiceData invoice, params string[] terms)
{
var filteredTerms = terms.Where(t => !string.IsNullOrWhiteSpace(t)
&& (invoice.InvoiceSearchData == null || invoice.InvoiceSearchData.All(data => data.Value != t)))
.Distinct()
.Select(s => new InvoiceSearchData() { InvoiceDataId = invoice.Id, Value = s.Truncate(512) });
context.AddRange(filteredTerms);
}
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
{
using (var context = _applicationDbContextFactory.CreateContext())
{
2018-05-16 10:26:45 +09:00
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
if (invoiceData == null)
return;
invoiceData.Status = InvoiceState.ToString(invoiceState.Status);
invoiceData.ExceptionStatus = InvoiceState.ToString(invoiceState.ExceptionStatus);
await context.SaveChangesAsync().ConfigureAwait(false);
}
}
2021-08-03 17:03:00 +09:00
internal async Task UpdateInvoicePrice(string invoiceId, InvoiceEntity invoice)
{
if (invoice.Type != InvoiceType.TopUp)
throw new ArgumentException("The invoice type should be TopUp to be able to update invoice price", nameof(invoice));
using (var context = _applicationDbContextFactory.CreateContext())
2021-08-03 17:03:00 +09:00
{
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
if (invoiceData == null)
return;
var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
2021-08-03 17:03:00 +09:00
blob.Price = invoice.Price;
AddToTextSearch(context, invoiceData, new[] { invoice.Price.ToString(CultureInfo.InvariantCulture) });
invoiceData.Blob = ToBytes(blob, null);
await context.SaveChangesAsync().ConfigureAwait(false);
}
}
public async Task MassArchive(string[] invoiceIds)
{
using (var context = _applicationDbContextFactory.CreateContext())
{
var items = context.Invoices.Where(a => invoiceIds.Contains(a.Id));
if (items == null)
{
return;
}
foreach (InvoiceData invoice in items)
{
invoice.Archived = true;
}
await context.SaveChangesAsync();
}
}
2020-07-24 08:13:21 +02:00
public async Task ToggleInvoiceArchival(string invoiceId, bool archived, string storeId = null)
2020-05-07 12:50:07 +02:00
{
using (var context = _applicationDbContextFactory.CreateContext())
2020-05-07 12:50:07 +02:00
{
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
2020-07-24 08:13:21 +02:00
if (invoiceData == null || invoiceData.Archived == archived ||
(storeId != null &&
2020-07-24 12:46:46 +02:00
!invoiceData.StoreDataId.Equals(storeId, StringComparison.InvariantCultureIgnoreCase)))
2020-05-07 12:50:07 +02:00
return;
invoiceData.Archived = archived;
await context.SaveChangesAsync().ConfigureAwait(false);
}
}
public async Task<InvoiceEntity> UpdateInvoiceMetadata(string invoiceId, string storeId, JObject metadata)
{
using (var context = _applicationDbContextFactory.CreateContext())
{
var invoiceData = await GetInvoiceRaw(invoiceId, context);
if (invoiceData == null || (storeId != null &&
!invoiceData.StoreDataId.Equals(storeId,
StringComparison.InvariantCultureIgnoreCase)))
return null;
var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
blob.Metadata = InvoiceMetadata.FromJObject(metadata);
invoiceData.Blob = ToBytes(blob);
await context.SaveChangesAsync().ConfigureAwait(false);
return ToEntity(invoiceData);
}
}
2020-07-24 09:40:37 +02:00
public async Task<bool> MarkInvoiceStatus(string invoiceId, InvoiceStatus status)
{
using (var context = _applicationDbContextFactory.CreateContext())
{
var invoiceData = await GetInvoiceRaw(invoiceId, context);
2020-07-24 09:40:37 +02:00
if (invoiceData == null)
{
return false;
}
2020-07-24 12:46:46 +02:00
context.Attach(invoiceData);
2020-07-24 09:40:37 +02:00
string eventName;
string legacyStatus;
2020-07-24 09:40:37 +02:00
switch (status)
{
case InvoiceStatus.Settled:
2020-07-24 09:40:37 +02:00
if (!invoiceData.GetInvoiceState().CanMarkComplete())
{
return false;
}
eventName = InvoiceEvent.MarkedCompleted;
legacyStatus = InvoiceStatusLegacy.Complete.ToString();
2020-07-24 09:40:37 +02:00
break;
case InvoiceStatus.Invalid:
if (!invoiceData.GetInvoiceState().CanMarkInvalid())
{
return false;
}
eventName = InvoiceEvent.MarkedInvalid;
legacyStatus = InvoiceStatusLegacy.Invalid.ToString();
2020-07-24 09:40:37 +02:00
break;
default:
return false;
}
2020-07-24 12:46:46 +02:00
invoiceData.Status = legacyStatus.ToLowerInvariant();
2020-07-24 09:40:37 +02:00
invoiceData.ExceptionStatus = InvoiceExceptionStatus.Marked.ToString().ToLowerInvariant();
_eventAggregator.Publish(new InvoiceEvent(ToEntity(invoiceData), eventName));
2020-07-24 12:46:46 +02:00
await context.SaveChangesAsync();
}
2020-07-24 09:40:37 +02:00
return true;
}
2020-07-24 12:46:46 +02:00
2021-10-23 22:28:50 -07:00
public async Task<InvoiceEntity> GetInvoice(string id, bool includeAddressData = false)
2020-07-24 12:46:46 +02:00
{
using (var context = _applicationDbContextFactory.CreateContext())
{
2021-10-23 22:28:50 -07:00
var res = await GetInvoiceRaw(id, context, includeAddressData);
return res == null ? null : ToEntity(res);
}
2020-07-24 12:46:46 +02:00
}
public async Task<InvoiceEntity[]> GetInvoices(string[] invoiceIds)
{
var invoiceIdSet = invoiceIds.ToHashSet();
using (var context = _applicationDbContextFactory.CreateContext())
{
IQueryable<Data.InvoiceData> query =
context
.Invoices
.Include(o => o.Payments)
.Where(o => invoiceIdSet.Contains(o.Id));
return (await query.ToListAsync()).Select(o => ToEntity(o)).ToArray();
}
}
2020-07-24 12:46:46 +02:00
2021-10-23 22:28:50 -07:00
private async Task<InvoiceData> GetInvoiceRaw(string id, ApplicationDbContext dbContext, bool includeAddressData = false)
{
IQueryable<Data.InvoiceData> query =
dbContext
.Invoices
2020-06-25 13:32:13 +09:00
.Include(o => o.Payments);
2021-10-23 22:28:50 -07:00
if (includeAddressData)
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
query = query.Where(i => i.Id == id);
var invoice = (await query.ToListAsync()).FirstOrDefault();
if (invoice == null)
return null;
return invoice;
}
2018-05-16 10:26:45 +09:00
private InvoiceEntity ToEntity(Data.InvoiceData invoice)
{
var entity = invoice.GetBlob(_btcPayNetworkProvider);
PaymentMethodDictionary paymentMethods = null;
2018-01-10 18:30:45 +09:00
#pragma warning disable CS0618
entity.Payments = invoice.Payments.Select(p =>
2017-11-06 00:31:02 -08:00
{
var paymentEntity = p.GetBlob(_btcPayNetworkProvider);
if (paymentEntity is null)
return null;
// PaymentEntity on version 0 does not have their own fee, because it was assumed that the payment method have fixed fee.
// We want to hide this legacy detail in InvoiceRepository, so we fetch the fee from the PaymentMethod and assign it to the PaymentEntity.
if (paymentEntity.Version == 0)
{
if (paymentMethods == null)
paymentMethods = entity.GetPaymentMethods();
var paymentMethodDetails = paymentMethods.TryGet(paymentEntity.GetPaymentMethodId())?.GetPaymentMethodDetails();
if (paymentMethodDetails != null) // == null should never happen, but we never know.
2019-01-07 15:35:18 +09:00
paymentEntity.NetworkFee = paymentMethodDetails.GetNextNetworkFee();
}
2017-11-06 00:31:02 -08:00
return paymentEntity;
})
.Where(p => p != null)
.OrderBy(a => a.ReceivedTime).ToList();
2018-01-10 18:30:45 +09:00
#pragma warning restore CS0618
var state = invoice.GetInvoiceState();
entity.ExceptionStatus = state.ExceptionStatus;
entity.Status = state.Status;
entity.RefundMail = invoice.CustomerEmail;
2020-06-25 13:32:13 +09:00
entity.Refundable = false;
if (invoice.HistoricalAddressInvoices != null)
{
entity.HistoricalAddresses = invoice.HistoricalAddressInvoices.ToArray();
}
2017-11-06 00:31:02 -08:00
if (invoice.AddressInvoices != null)
{
entity.AvailableAddressHashes = invoice.AddressInvoices.Select(a => a.GetAddress() + a.GetpaymentMethodId().ToString()).ToHashSet();
2017-11-06 00:31:02 -08:00
}
if (invoice.Events != null)
2018-01-14 21:48:23 +09:00
{
entity.Events = invoice.Events.OrderBy(c => c.Timestamp).ToList();
}
if (!string.IsNullOrEmpty(entity.RefundMail) && string.IsNullOrEmpty(entity.Metadata.BuyerEmail))
{
entity.Metadata.BuyerEmail = entity.RefundMail;
}
2020-05-07 12:50:07 +02:00
entity.Archived = invoice.Archived;
return entity;
}
private IQueryable<Data.InvoiceData> GetInvoiceQuery(ApplicationDbContext context, InvoiceQuery queryObject)
{
IQueryable<Data.InvoiceData> query = queryObject.UserId is null
? context.Invoices
: context.UserStore
.Where(u => u.ApplicationUserId == queryObject.UserId)
.SelectMany(c => c.StoreData.Invoices);
2020-05-07 12:50:07 +02:00
if (!queryObject.IncludeArchived)
{
query = query.Where(i => !i.Archived);
}
2020-06-28 17:55:27 +09:00
if (queryObject.InvoiceId != null && queryObject.InvoiceId.Length > 0)
{
var statusSet = queryObject.InvoiceId.ToHashSet().ToArray();
query = query.Where(i => statusSet.Contains(i.Id));
}
2020-06-28 17:55:27 +09:00
if (queryObject.StoreId != null && queryObject.StoreId.Length > 0)
{
var stores = queryObject.StoreId.ToHashSet().ToArray();
query = query.Where(i => stores.Contains(i.StoreDataId));
}
if (!string.IsNullOrEmpty(queryObject.TextSearch))
{
var text = queryObject.TextSearch.Truncate(512);
2021-10-06 12:53:41 +09:00
#pragma warning disable CA1310 // Specify StringComparison
query = query.Where(i => i.InvoiceSearchData.Any(data => data.Value.StartsWith(text)));
2021-10-06 12:53:41 +09:00
#pragma warning restore CA1310 // Specify StringComparison
}
if (queryObject.StartDate != null)
query = query.Where(i => queryObject.StartDate.Value <= i.Created);
if (queryObject.EndDate != null)
query = query.Where(i => i.Created <= queryObject.EndDate.Value);
if (queryObject.OrderId != null && queryObject.OrderId.Length > 0)
{
var statusSet = queryObject.OrderId.ToHashSet().ToArray();
query = query.Where(i => statusSet.Contains(i.OrderId));
}
if (queryObject.ItemCode != null && queryObject.ItemCode.Length > 0)
{
var statusSet = queryObject.ItemCode.ToHashSet().ToArray();
query = query.Where(i => statusSet.Contains(i.ItemCode));
}
if (queryObject.Status != null && queryObject.Status.Length > 0)
{
var statusSet = queryObject.Status.ToHashSet().ToArray();
query = query.Where(i => statusSet.Contains(i.Status));
}
if (queryObject.Unusual != null)
{
var unused = queryObject.Unusual.Value;
2019-04-13 13:50:14 +02:00
query = query.Where(i => unused == (i.Status == "invalid" || !string.IsNullOrEmpty(i.ExceptionStatus)));
}
2018-05-06 13:16:39 +09:00
if (queryObject.ExceptionStatus != null && queryObject.ExceptionStatus.Length > 0)
{
2019-10-09 17:06:00 +09:00
var exceptionStatusSet = queryObject.ExceptionStatus.Select(s => NormalizeExceptionStatus(s)).ToHashSet().ToArray();
query = query.Where(i => exceptionStatusSet.Contains(i.ExceptionStatus));
}
query = query.OrderByDescending(q => q.Created);
if (queryObject.Skip != null)
query = query.Skip(queryObject.Skip.Value);
if (queryObject.Take != null)
query = query.Take(queryObject.Take.Value);
return query;
}
public async Task<int> GetInvoicesTotal(InvoiceQuery queryObject)
{
using (var context = _applicationDbContextFactory.CreateContext())
{
var query = GetInvoiceQuery(context, queryObject);
return await query.CountAsync();
}
}
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
{
using (var context = _applicationDbContextFactory.CreateContext())
{
var query = GetInvoiceQuery(context, queryObject);
2020-06-25 13:32:13 +09:00
query = query.Include(o => o.Payments);
if (queryObject.IncludeAddresses)
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
if (queryObject.IncludeEvents)
query = query.Include(o => o.Events);
var data = await query.ToArrayAsync().ConfigureAwait(false);
return data.Select(ToEntity).ToArray();
}
}
private string NormalizeExceptionStatus(string status)
{
status = status.ToLowerInvariant();
switch (status)
{
case "paidover":
case "over":
case "overpaid":
status = "paidOver";
break;
case "paidlate":
case "late":
status = "paidLate";
break;
case "paidpartial":
case "underpaid":
case "partial":
status = "paidPartial";
break;
}
return status;
}
internal static byte[] ToBytes<T>(T obj, BTCPayNetworkBase network = null)
{
return ZipUtils.Zip(ToJsonString(obj, network));
}
public static string ToJsonString<T>(T data, BTCPayNetworkBase network)
{
if (network == null)
{
return JsonConvert.SerializeObject(data, DefaultSerializerSettings);
}
return network.ToString(data);
}
}
public class InvoiceQuery
{
public string[] StoreId
{
get; set;
}
public string UserId
{
get; set;
}
public string TextSearch
{
get; set;
}
public DateTimeOffset? StartDate
{
get; set;
}
public DateTimeOffset? EndDate
{
get; set;
}
public int? Skip
{
get; set;
}
public int? Take
{
get; set;
}
2019-01-06 10:00:55 +01:00
public string[] OrderId
{
get; set;
}
2019-01-06 10:00:55 +01:00
public string[] ItemCode
{
get; set;
}
2018-05-06 13:16:39 +09:00
public bool? Unusual { get; set; }
public string[] Status
{
get; set;
}
public string[] ExceptionStatus
{
get; set;
}
public string[] InvoiceId
{
get;
set;
}
public bool IncludeAddresses { get; set; }
2018-01-14 21:48:23 +09:00
public bool IncludeEvents { get; set; }
2020-05-07 12:50:07 +02:00
public bool IncludeArchived { get; set; } = true;
}
2017-09-13 15:47:34 +09:00
}