From 8e30b7430dc101d40334ee238413f177fa31be5a Mon Sep 17 00:00:00 2001 From: rockstardev Date: Fri, 30 Nov 2018 02:04:26 -0600 Subject: [PATCH] Adding PaymentType and destination, CSV export --- .../Controllers/InvoiceController.UI.cs | 12 +- .../Services/Invoices/Export/CsvSerializer.cs | 152 ++++++++++++++++++ .../Invoices/Export/InvoiceExport.cs} | 40 +++-- .../Views/Invoice/ListInvoices.cshtml | 3 +- 4 files changed, 191 insertions(+), 16 deletions(-) create mode 100644 BTCPayServer/Services/Invoices/Export/CsvSerializer.cs rename BTCPayServer/{Models/InvoicingModels/ExportInvoicesModel.cs => Services/Invoices/Export/InvoiceExport.cs} (77%) diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 71cbbdb96..6d37559ee 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net.Mime; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; @@ -14,6 +15,7 @@ using BTCPayServer.Payments.Changelly; using BTCPayServer.Payments.Lightning; using BTCPayServer.Security; using BTCPayServer.Services.Invoices; +using BTCPayServer.Services.Invoices.Export; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; @@ -474,10 +476,18 @@ namespace BTCPayServer.Controllers [BitpayAPIConstraint(false)] public async Task Export(string format, string searchTerm = null) { - var model = new ExportInvoicesModel(); + var model = new InvoiceExport(); var invoices = await ListInvoicesProcess(searchTerm, 0, int.MaxValue); var res = model.Process(invoices, format); + + var cd = new ContentDisposition + { + FileName = $"btcpay-export-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}.{format}", + Inline = true + }; + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("X-Content-Type-Options", "nosniff"); return Content(res, "application/" + format); } diff --git a/BTCPayServer/Services/Invoices/Export/CsvSerializer.cs b/BTCPayServer/Services/Invoices/Export/CsvSerializer.cs new file mode 100644 index 000000000..0238ae1ae --- /dev/null +++ b/BTCPayServer/Services/Invoices/Export/CsvSerializer.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +// Ref: https://www.codeproject.com/Articles/566656/CSV-Serializer-for-NET +namespace BTCPayServer.Services.Invoices.Export +{ + /// + /// Serialize and Deserialize Lists of any object type to CSV. + /// +#pragma warning disable CA1305 // Specify IFormatProvider + public class CsvSerializer where T : class, new() + { + private List _properties; + + public bool IgnoreEmptyLines { get; set; } = true; + public bool IgnoreReferenceTypesExceptString { get; set; } = true; + public string NewlineReplacement { get; set; } = ((char)0x254).ToString(); + + public char Separator { get; set; } = ','; + public string Replacement { get; set; } = ((char)0x255).ToString(); + + public string RowNumberColumnTitle { get; set; } = "RowNumber"; + public bool UseLineNumbers { get; set; } = false; + public bool UseTextQualifier { get; set; } = false; + public bool UseEofLiteral { get; set; } = false; + + /// + /// Csv Serializer + /// Initialize by selected properties from the type to be de/serialized + /// + public CsvSerializer() + { + var type = typeof(T); + + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance + | BindingFlags.GetProperty | BindingFlags.SetProperty); + + + var q = properties.AsQueryable(); + + if (IgnoreReferenceTypesExceptString) + { + q = q.Where(a => a.PropertyType.IsValueType || a.PropertyType.Name == "String"); + } + + var r = from a in q + where a.GetCustomAttribute() == null + select a; + + _properties = r.ToList(); + } + + /// + /// Serialize + /// + /// stream + /// data + public string Serialize(IList data) + { + var sb = new StringBuilder(); + var values = new List(); + + sb.AppendLine(GetHeader()); + + var row = 1; + foreach (var item in data) + { + values.Clear(); + + if (UseLineNumbers) + { + values.Add(row++.ToString()); + } + + foreach (var p in _properties) + { + var raw = p.GetValue(item); + var value = raw == null ? "" : + raw.ToString() + .Replace(Separator.ToString(), Replacement) + .Replace(Environment.NewLine, NewlineReplacement); + + if (UseTextQualifier) + { + value = string.Format("\"{0}\"", value); + } + + values.Add(value); + } + sb.AppendLine(string.Join(Separator.ToString(), values.ToArray())); + } + + if (UseEofLiteral) + { + values.Clear(); + + if (UseLineNumbers) + { + values.Add(row++.ToString()); + } + + values.Add("EOF"); + + sb.AppendLine(string.Join(Separator.ToString(), values.ToArray())); + } + + return sb.ToString(); + } + + /// + /// Get Header + /// + /// + private string GetHeader() + { + var header = _properties.Select(a => a.Name); + + if (UseLineNumbers) + { + header = new string[] { RowNumberColumnTitle }.Union(header); + } + + return string.Join(Separator.ToString(), header.ToArray()); + } + } +#pragma warning restore CA1305 // Specify IFormatProvider + + public class CsvIgnoreAttribute : Attribute { } + + public class InvalidCsvFormatException : Exception + { + /// + /// Invalid Csv Format Exception + /// + /// message + public InvalidCsvFormatException(string message) + : base(message) + { + } + + public InvalidCsvFormatException(string message, Exception ex) + : base(message, ex) + { + } + } +} diff --git a/BTCPayServer/Models/InvoicingModels/ExportInvoicesModel.cs b/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs similarity index 77% rename from BTCPayServer/Models/InvoicingModels/ExportInvoicesModel.cs rename to BTCPayServer/Services/Invoices/Export/InvoiceExport.cs index 85f5f8a23..c03d4588a 100644 --- a/BTCPayServer/Models/InvoicingModels/ExportInvoicesModel.cs +++ b/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs @@ -6,19 +6,11 @@ using BTCPayServer.Payments.Bitcoin; using BTCPayServer.Services.Invoices; using Newtonsoft.Json; -namespace BTCPayServer.Models.InvoicingModels +namespace BTCPayServer.Services.Invoices.Export { - public class ExportInvoicesModel + public class InvoiceExport { public string Process(InvoiceEntity[] invoices, string fileFormat) - { - if (String.Equals(fileFormat, "json", StringComparison.OrdinalIgnoreCase)) - return processJson(invoices); - else - throw new Exception("Export format not supported"); - } - - private string processJson(InvoiceEntity[] invoices) { var csvInvoices = new List(); foreach (var i in invoices) @@ -26,12 +18,30 @@ namespace BTCPayServer.Models.InvoicingModels csvInvoices.AddRange(convertFromDb(i)); } + if (String.Equals(fileFormat, "json", StringComparison.OrdinalIgnoreCase)) + return processJson(csvInvoices); + else if (String.Equals(fileFormat, "csv", StringComparison.OrdinalIgnoreCase)) + return processCsv(csvInvoices); + else + throw new Exception("Export format not supported"); + } + + private string processJson(List invoices) + { var serializerSett = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; - var json = JsonConvert.SerializeObject(csvInvoices, Formatting.Indented, serializerSett); + var json = JsonConvert.SerializeObject(invoices, Formatting.Indented, serializerSett); return json; } + private string processCsv(List invoices) + { + var serializer = new CsvSerializer(); + var csv = serializer.Serialize(invoices); + + return csv; + } + private IEnumerable convertFromDb(InvoiceEntity invoice) { var exportList = new List(); @@ -43,14 +53,15 @@ namespace BTCPayServer.Models.InvoicingModels var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), null); var accounting = pmethod.Calculate(); - var onchainDetails = pmethod.GetPaymentMethodDetails() as BitcoinLikeOnChainPaymentMethod; + var details = pmethod.GetPaymentMethodDetails(); var target = new ExportInvoiceHolder { PaymentId = pdata.GetPaymentId(), CryptoCode = cryptoCode, ConversionRate = pmethod.Rate, - Address = onchainDetails?.DepositAddress, + PaymentType = details.GetPaymentType().ToString(), + Destination = details.GetPaymentDestination(), PaymentDue = $"{accounting.MinimumTotalDue} {cryptoCode}", PaymentPaid = $"{accounting.CryptoPaid} {cryptoCode}", PaymentOverpaid = $"{accounting.OverpaidHelper} {cryptoCode}", @@ -78,7 +89,8 @@ namespace BTCPayServer.Models.InvoicingModels public string PaymentId { get; set; } public string CryptoCode { get; set; } public decimal ConversionRate { get; set; } - public string Address { get; set; } + public string PaymentType { get; set; } + public string Destination { get; set; } public string PaymentDue { get; set; } public string PaymentPaid { get; set; } public string PaymentOverpaid { get; set; } diff --git a/BTCPayServer/Views/Invoice/ListInvoices.cshtml b/BTCPayServer/Views/Invoice/ListInvoices.cshtml index 803c10325..9c1061389 100644 --- a/BTCPayServer/Views/Invoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/Invoice/ListInvoices.cshtml @@ -48,7 +48,8 @@ Export