2017-09-13 08:47:34 +02:00
|
|
|
|
using DBreeze;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using NBitpayClient;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using NBitcoin;
|
|
|
|
|
using NBitcoin.DataEncoders;
|
|
|
|
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using Microsoft.EntityFrameworkCore.Query;
|
|
|
|
|
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
|
|
|
|
|
using BTCPayServer.Models;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using BTCPayServer.Data;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using BTCPayServer.Models.InvoicingModels;
|
|
|
|
|
|
2017-10-20 21:06:37 +02:00
|
|
|
|
namespace BTCPayServer.Services.Invoices
|
2017-09-13 08:47:34 +02:00
|
|
|
|
{
|
2017-11-12 15:03:33 +01:00
|
|
|
|
public class InvoiceRepository : IDisposable
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private readonly DBreezeEngine _Engine;
|
|
|
|
|
public DBreezeEngine Engine
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _Engine;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Network _Network;
|
|
|
|
|
public Network Network
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _Network;
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
_Network = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-21 07:52:04 +01:00
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
private ApplicationDbContextFactory _ContextFactory;
|
2017-11-12 15:03:33 +01:00
|
|
|
|
private CustomThreadPool _IndexerThread;
|
|
|
|
|
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath, Network network)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2017-11-12 15:03:33 +01:00
|
|
|
|
_Engine = new DBreezeEngine(dbreezePath);
|
|
|
|
|
_IndexerThread = new CustomThreadPool(1, "Invoice Indexer");
|
2017-10-27 10:53:04 +02:00
|
|
|
|
_Network = network;
|
|
|
|
|
_ContextFactory = contextFactory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<bool> RemovePendingInvoice(string invoiceId)
|
|
|
|
|
{
|
|
|
|
|
using (var ctx = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (DbUpdateException) { return false; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<string> GetInvoiceIdFromScriptPubKey(Script scriptPubKey)
|
|
|
|
|
{
|
|
|
|
|
using (var db = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString());
|
|
|
|
|
return result?.InvoiceDataId;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<string[]> GetPendingInvoices()
|
|
|
|
|
{
|
|
|
|
|
using (var ctx = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
return await ctx.PendingInvoices.Select(p => p.Id).ToArrayAsync();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
|
|
|
|
|
{
|
|
|
|
|
using (var ctx = _ContextFactory.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;
|
|
|
|
|
}
|
|
|
|
|
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, BTCPayNetworkProvider networkProvider)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2017-12-21 07:52:04 +01:00
|
|
|
|
List<string> textSearch = new List<string>();
|
2017-10-27 10:53:04 +02:00
|
|
|
|
invoice = Clone(invoice);
|
|
|
|
|
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
|
|
|
|
invoice.Payments = new List<PaymentEntity>();
|
|
|
|
|
invoice.StoreId = storeId;
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
2017-11-12 15:51:14 +01:00
|
|
|
|
context.Invoices.Add(new InvoiceData()
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
|
|
|
|
StoreDataId = storeId,
|
|
|
|
|
Id = invoice.Id,
|
|
|
|
|
Created = invoice.InvoiceTime,
|
|
|
|
|
Blob = ToBytes(invoice),
|
|
|
|
|
OrderId = invoice.OrderId,
|
|
|
|
|
Status = invoice.Status,
|
|
|
|
|
ItemCode = invoice.ProductInformation.ItemCode,
|
|
|
|
|
CustomerEmail = invoice.RefundMail
|
2017-11-12 15:37:21 +01:00
|
|
|
|
});
|
2017-12-21 07:52:04 +01:00
|
|
|
|
|
|
|
|
|
foreach (var cryptoData in invoice.GetCryptoData().Values)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2017-12-21 07:52:04 +01:00
|
|
|
|
var network = networkProvider.GetNetwork(cryptoData.CryptoCode);
|
|
|
|
|
if (network == null)
|
|
|
|
|
throw new InvalidOperationException("CryptoCode unsupported");
|
|
|
|
|
context.AddressInvoices.Add(new AddressInvoiceData()
|
|
|
|
|
{
|
|
|
|
|
Address = BitcoinAddress.Create(cryptoData.DepositAddress, network.NBitcoinNetwork).ScriptPubKey.Hash.ToString(),
|
|
|
|
|
InvoiceDataId = invoice.Id,
|
|
|
|
|
CreatedTime = DateTimeOffset.UtcNow,
|
|
|
|
|
});
|
|
|
|
|
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
|
|
|
|
{
|
|
|
|
|
InvoiceDataId = invoice.Id,
|
|
|
|
|
Address = cryptoData.DepositAddress,
|
|
|
|
|
#pragma warning disable CS0618
|
|
|
|
|
CryptoCode = cryptoData.CryptoCode,
|
|
|
|
|
#pragma warning restore CS0618
|
|
|
|
|
Assigned = DateTimeOffset.UtcNow
|
|
|
|
|
});
|
|
|
|
|
textSearch.Add(cryptoData.DepositAddress);
|
2017-12-21 10:01:26 +01:00
|
|
|
|
textSearch.Add(cryptoData.Calculate().TotalDue.ToString());
|
2017-12-21 07:52:04 +01:00
|
|
|
|
}
|
2017-11-12 15:51:14 +01:00
|
|
|
|
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id });
|
2017-10-27 10:53:04 +02:00
|
|
|
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
textSearch.Add(invoice.Id);
|
|
|
|
|
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
|
|
|
|
textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
|
|
|
|
|
textSearch.Add(invoice.OrderId);
|
|
|
|
|
textSearch.Add(ToString(invoice.BuyerInformation));
|
|
|
|
|
textSearch.Add(ToString(invoice.ProductInformation));
|
|
|
|
|
textSearch.Add(invoice.StoreId);
|
|
|
|
|
|
|
|
|
|
AddToTextSearch(invoice.Id, textSearch.ToArray());
|
2017-10-27 10:53:04 +02:00
|
|
|
|
|
|
|
|
|
return invoice;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
public async Task<bool> NewAddress(string invoiceId, BitcoinAddress bitcoinAddress, BTCPayNetwork network)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
var invoice = await context.Invoices.FirstOrDefaultAsync(i => i.Id == invoiceId);
|
|
|
|
|
if (invoice == null)
|
|
|
|
|
return false;
|
2017-12-21 07:52:04 +01:00
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob);
|
2017-12-21 07:52:04 +01:00
|
|
|
|
var cryptoData = invoiceEntity.GetCryptoData();
|
|
|
|
|
var currencyData = cryptoData.Where(c => c.Value.CryptoCode == network.CryptoCode).Select(f => f.Value).FirstOrDefault();
|
|
|
|
|
if (currencyData == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (currencyData.DepositAddress != null)
|
|
|
|
|
{
|
|
|
|
|
MarkUnassigned(invoiceId, invoiceEntity, context, network.CryptoCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currencyData.DepositAddress = bitcoinAddress.ToString();
|
|
|
|
|
|
|
|
|
|
#pragma warning disable CS0618
|
|
|
|
|
if (network.CryptoCode == "BTC")
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2017-12-21 07:52:04 +01:00
|
|
|
|
invoiceEntity.DepositAddress = currencyData.DepositAddress;
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
2017-12-21 07:52:04 +01:00
|
|
|
|
#pragma warning disable CS0618
|
|
|
|
|
invoiceEntity.SetCryptoData(cryptoData);
|
|
|
|
|
invoice.Blob = ToBytes(invoiceEntity);
|
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
context.AddressInvoices.Add(new AddressInvoiceData() { Address = bitcoinAddress.ScriptPubKey.Hash.ToString(), InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow });
|
|
|
|
|
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
|
|
|
|
{
|
|
|
|
|
InvoiceDataId = invoiceId,
|
|
|
|
|
Address = bitcoinAddress.ToString(),
|
|
|
|
|
Assigned = DateTimeOffset.UtcNow
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
|
AddToTextSearch(invoice.Id, bitcoinAddress.ToString());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-21 07:52:04 +01:00
|
|
|
|
private static void MarkUnassigned(string invoiceId, InvoiceEntity entity, ApplicationDbContext context, string cryptoCode)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2017-12-21 07:52:04 +01:00
|
|
|
|
foreach (var address in entity.GetCryptoData())
|
|
|
|
|
{
|
|
|
|
|
if (cryptoCode != null && cryptoCode != address.Value.CryptoCode)
|
|
|
|
|
continue;
|
|
|
|
|
var historical = new HistoricalAddressInvoiceData();
|
|
|
|
|
historical.InvoiceDataId = invoiceId;
|
|
|
|
|
historical.Address = address.Value.DepositAddress;
|
|
|
|
|
historical.UnAssigned = DateTimeOffset.UtcNow;
|
|
|
|
|
context.Attach(historical);
|
|
|
|
|
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
|
|
|
|
}
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task UnaffectAddress(string invoiceId)
|
|
|
|
|
{
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
|
|
|
|
if (invoiceData == null)
|
|
|
|
|
return;
|
|
|
|
|
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob);
|
2017-12-21 07:52:04 +01:00
|
|
|
|
MarkUnassigned(invoiceId, invoiceEntity, context, null);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
catch (DbUpdateException) { } //Possibly, it was unassigned before
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string[] SearchInvoice(string searchTerms)
|
|
|
|
|
{
|
|
|
|
|
using (var tx = _Engine.GetTransaction())
|
|
|
|
|
{
|
|
|
|
|
return tx.TextSearch("InvoiceSearch").Block(searchTerms)
|
|
|
|
|
.GetDocumentIDs()
|
|
|
|
|
.Select(id => Encoders.Base58.EncodeData(id))
|
|
|
|
|
.ToArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AddToTextSearch(string invoiceId, params string[] terms)
|
|
|
|
|
{
|
2017-12-21 07:52:04 +01:00
|
|
|
|
_IndexerThread.DoAsync(() =>
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
2017-11-12 15:03:33 +01:00
|
|
|
|
using (var tx = _Engine.GetTransaction())
|
|
|
|
|
{
|
2018-01-04 14:21:47 +01:00
|
|
|
|
tx.TextAppend("InvoiceSearch", Encoders.Base58.DecodeData(invoiceId), string.Join(" ", terms.Where(t => !String.IsNullOrWhiteSpace(t))));
|
2017-11-12 15:03:33 +01:00
|
|
|
|
tx.Commit();
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task UpdateInvoiceStatus(string invoiceId, string status, string exceptionStatus)
|
|
|
|
|
{
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
|
|
|
|
if (invoiceData == null)
|
|
|
|
|
return;
|
|
|
|
|
invoiceData.Status = status;
|
|
|
|
|
invoiceData.ExceptionStatus = exceptionStatus;
|
|
|
|
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-06 04:15:52 +01:00
|
|
|
|
public async Task UpdatePaidInvoiceToInvalid(string invoiceId)
|
|
|
|
|
{
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
2017-11-06 14:43:24 +01:00
|
|
|
|
if (invoiceData?.Status != "paid")
|
2017-11-06 04:15:52 +01:00
|
|
|
|
return;
|
|
|
|
|
invoiceData.Status = "invalid";
|
|
|
|
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-11-06 09:31:02 +01:00
|
|
|
|
public async Task<InvoiceEntity> GetInvoice(string storeId, string id, bool inludeAddressData = false)
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
IQueryable<InvoiceData> query =
|
|
|
|
|
context
|
|
|
|
|
.Invoices
|
|
|
|
|
.Include(o => o.Payments)
|
|
|
|
|
.Include(o => o.RefundAddresses);
|
2017-11-06 09:31:02 +01:00
|
|
|
|
if (inludeAddressData)
|
|
|
|
|
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
query = query.Where(i => i.Id == id);
|
|
|
|
|
|
|
|
|
|
if (storeId != null)
|
|
|
|
|
query = query.Where(i => i.StoreDataId == storeId);
|
|
|
|
|
|
|
|
|
|
var invoice = await query.FirstOrDefaultAsync().ConfigureAwait(false);
|
|
|
|
|
if (invoice == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return ToEntity(invoice);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private InvoiceEntity ToEntity(InvoiceData invoice)
|
|
|
|
|
{
|
|
|
|
|
var entity = ToObject<InvoiceEntity>(invoice.Blob);
|
2017-12-21 07:52:04 +01:00
|
|
|
|
entity.Payments = invoice.Payments.Select(p =>
|
2017-11-06 09:31:02 +01:00
|
|
|
|
{
|
|
|
|
|
var paymentEntity = ToObject<PaymentEntity>(p.Blob);
|
|
|
|
|
paymentEntity.Accounted = p.Accounted;
|
|
|
|
|
return paymentEntity;
|
|
|
|
|
}).ToList();
|
2017-10-27 10:53:04 +02:00
|
|
|
|
entity.ExceptionStatus = invoice.ExceptionStatus;
|
|
|
|
|
entity.Status = invoice.Status;
|
|
|
|
|
entity.RefundMail = invoice.CustomerEmail;
|
|
|
|
|
entity.Refundable = invoice.RefundAddresses.Count != 0;
|
|
|
|
|
if (invoice.HistoricalAddressInvoices != null)
|
|
|
|
|
{
|
|
|
|
|
entity.HistoricalAddresses = invoice.HistoricalAddressInvoices.ToArray();
|
|
|
|
|
}
|
2017-11-06 09:31:02 +01:00
|
|
|
|
if (invoice.AddressInvoices != null)
|
|
|
|
|
{
|
|
|
|
|
entity.AvailableAddressHashes = invoice.AddressInvoices.Select(a => a.Address).ToHashSet();
|
|
|
|
|
}
|
2017-10-27 10:53:04 +02:00
|
|
|
|
return entity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
|
|
|
|
|
{
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
IQueryable<InvoiceData> query = context
|
|
|
|
|
.Invoices
|
|
|
|
|
.Include(o => o.Payments)
|
|
|
|
|
.Include(o => o.RefundAddresses);
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(queryObject.InvoiceId))
|
|
|
|
|
{
|
|
|
|
|
query = query.Where(i => i.Id == queryObject.InvoiceId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(queryObject.StoreId))
|
|
|
|
|
{
|
|
|
|
|
query = query.Where(i => i.StoreDataId == queryObject.StoreId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (queryObject.UserId != null)
|
|
|
|
|
{
|
|
|
|
|
query = query.Where(i => i.StoreData.UserStores.Any(u => u.ApplicationUserId == queryObject.UserId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(queryObject.TextSearch))
|
|
|
|
|
{
|
|
|
|
|
var ids = new HashSet<string>(SearchInvoice(queryObject.TextSearch));
|
|
|
|
|
if (ids.Count == 0)
|
|
|
|
|
return new InvoiceEntity[0];
|
|
|
|
|
query = query.Where(i => ids.Contains(i.Id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.ItemCode != null)
|
|
|
|
|
query = query.Where(i => i.ItemCode == queryObject.ItemCode);
|
|
|
|
|
|
|
|
|
|
if (queryObject.OrderId != null)
|
|
|
|
|
query = query.Where(i => i.OrderId == queryObject.OrderId);
|
|
|
|
|
|
|
|
|
|
if (queryObject.Status != null)
|
|
|
|
|
query = query.Where(i => i.Status == queryObject.Status);
|
|
|
|
|
|
|
|
|
|
query = query.OrderByDescending(q => q.Created);
|
|
|
|
|
|
|
|
|
|
if (queryObject.Skip != null)
|
|
|
|
|
query = query.Skip(queryObject.Skip.Value);
|
|
|
|
|
|
|
|
|
|
if (queryObject.Count != null)
|
|
|
|
|
query = query.Take(queryObject.Count.Value);
|
|
|
|
|
|
|
|
|
|
var data = await query.ToArrayAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
return data.Select(ToEntity).ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task AddRefundsAsync(string invoiceId, TxOut[] outputs)
|
|
|
|
|
{
|
|
|
|
|
if (outputs.Length == 0)
|
|
|
|
|
return;
|
|
|
|
|
outputs = outputs.Take(10).ToArray();
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
int i = 0;
|
|
|
|
|
foreach (var output in outputs)
|
|
|
|
|
{
|
2017-11-12 15:37:21 +01:00
|
|
|
|
context.RefundAddresses.Add(new RefundAddressesData()
|
2017-10-27 10:53:04 +02:00
|
|
|
|
{
|
|
|
|
|
Id = invoiceId + "-" + i,
|
|
|
|
|
InvoiceDataId = invoiceId,
|
|
|
|
|
Blob = ToBytes(output)
|
2017-11-12 15:37:21 +01:00
|
|
|
|
});
|
2017-10-27 10:53:04 +02:00
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var addresses = outputs.Select(o => o.ScriptPubKey.GetDestinationAddress(_Network)).Where(a => a != null).ToArray();
|
|
|
|
|
AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<PaymentEntity> AddPayment(string invoiceId, Coin receivedCoin)
|
|
|
|
|
{
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
PaymentEntity entity = new PaymentEntity
|
|
|
|
|
{
|
|
|
|
|
Outpoint = receivedCoin.Outpoint,
|
|
|
|
|
Output = receivedCoin.TxOut,
|
|
|
|
|
ReceivedTime = DateTime.UtcNow
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PaymentData data = new PaymentData
|
|
|
|
|
{
|
|
|
|
|
Id = receivedCoin.Outpoint.ToString(),
|
|
|
|
|
Blob = ToBytes(entity),
|
|
|
|
|
InvoiceDataId = invoiceId
|
|
|
|
|
};
|
|
|
|
|
|
2017-11-12 15:37:21 +01:00
|
|
|
|
context.Payments.Add(data);
|
2017-10-27 10:53:04 +02:00
|
|
|
|
|
|
|
|
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
2017-11-06 09:31:02 +01:00
|
|
|
|
AddToTextSearch(invoiceId, receivedCoin.Outpoint.Hash.ToString());
|
2017-10-27 10:53:04 +02:00
|
|
|
|
return entity;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-06 09:31:02 +01:00
|
|
|
|
public async Task UpdatePayments(List<AccountedPaymentEntity> payments)
|
|
|
|
|
{
|
|
|
|
|
if (payments.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
using (var context = _ContextFactory.CreateContext())
|
|
|
|
|
{
|
|
|
|
|
foreach (var payment in payments)
|
|
|
|
|
{
|
|
|
|
|
var data = new PaymentData();
|
|
|
|
|
data.Id = payment.Payment.Outpoint.ToString();
|
|
|
|
|
data.Accounted = payment.Payment.Accounted;
|
|
|
|
|
context.Attach(data);
|
|
|
|
|
context.Entry(data).Property(o => o.Accounted).IsModified = true;
|
|
|
|
|
}
|
|
|
|
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-27 10:53:04 +02:00
|
|
|
|
private T ToObject<T>(byte[] value)
|
|
|
|
|
{
|
|
|
|
|
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ZipUtils.Unzip(value), Network);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private byte[] ToBytes<T>(T obj)
|
|
|
|
|
{
|
|
|
|
|
return ZipUtils.Zip(NBitcoin.JsonConverters.Serializer.ToString(obj));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private T Clone<T>(T invoice)
|
|
|
|
|
{
|
|
|
|
|
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ToString(invoice), Network);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string ToString<T>(T data)
|
|
|
|
|
{
|
|
|
|
|
return NBitcoin.JsonConverters.Serializer.ToString(data, Network);
|
|
|
|
|
}
|
2017-11-12 15:03:33 +01:00
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
if (_Engine != null)
|
|
|
|
|
_Engine.Dispose();
|
|
|
|
|
if (_IndexerThread != null)
|
|
|
|
|
_IndexerThread.Dispose();
|
|
|
|
|
}
|
2017-10-27 10:53:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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? Count
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string OrderId
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string ItemCode
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string Status
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
public string InvoiceId
|
|
|
|
|
{
|
|
|
|
|
get;
|
|
|
|
|
set;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-13 08:47:34 +02:00
|
|
|
|
}
|