mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Adding PaymentType and destination, CSV export
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Mime;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -14,6 +15,7 @@ using BTCPayServer.Payments.Changelly;
|
|||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using BTCPayServer.Services.Invoices.Export;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
@@ -474,10 +476,18 @@ namespace BTCPayServer.Controllers
|
|||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
public async Task<IActionResult> Export(string format, string searchTerm = null)
|
public async Task<IActionResult> Export(string format, string searchTerm = null)
|
||||||
{
|
{
|
||||||
var model = new ExportInvoicesModel();
|
var model = new InvoiceExport();
|
||||||
|
|
||||||
var invoices = await ListInvoicesProcess(searchTerm, 0, int.MaxValue);
|
var invoices = await ListInvoicesProcess(searchTerm, 0, int.MaxValue);
|
||||||
var res = model.Process(invoices, format);
|
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);
|
return Content(res, "application/" + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
152
BTCPayServer/Services/Invoices/Export/CsvSerializer.cs
Normal file
152
BTCPayServer/Services/Invoices/Export/CsvSerializer.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize and Deserialize Lists of any object type to CSV.
|
||||||
|
/// </summary>
|
||||||
|
#pragma warning disable CA1305 // Specify IFormatProvider
|
||||||
|
public class CsvSerializer<T> where T : class, new()
|
||||||
|
{
|
||||||
|
private List<PropertyInfo> _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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Csv Serializer
|
||||||
|
/// Initialize by selected properties from the type to be de/serialized
|
||||||
|
/// </summary>
|
||||||
|
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<CsvIgnoreAttribute>() == null
|
||||||
|
select a;
|
||||||
|
|
||||||
|
_properties = r.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">stream</param>
|
||||||
|
/// <param name="data">data</param>
|
||||||
|
public string Serialize(IList<T> data)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var values = new List<string>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get Header
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid Csv Format Exception
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">message</param>
|
||||||
|
public InvalidCsvFormatException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidCsvFormatException(string message, Exception ex)
|
||||||
|
: base(message, ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,19 +6,11 @@ using BTCPayServer.Payments.Bitcoin;
|
|||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using Newtonsoft.Json;
|
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)
|
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<ExportInvoiceHolder>();
|
var csvInvoices = new List<ExportInvoiceHolder>();
|
||||||
foreach (var i in invoices)
|
foreach (var i in invoices)
|
||||||
@@ -26,12 +18,30 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
csvInvoices.AddRange(convertFromDb(i));
|
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<ExportInvoiceHolder> invoices)
|
||||||
|
{
|
||||||
var serializerSett = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
|
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;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string processCsv(List<ExportInvoiceHolder> invoices)
|
||||||
|
{
|
||||||
|
var serializer = new CsvSerializer<ExportInvoiceHolder>();
|
||||||
|
var csv = serializer.Serialize(invoices);
|
||||||
|
|
||||||
|
return csv;
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<ExportInvoiceHolder> convertFromDb(InvoiceEntity invoice)
|
private IEnumerable<ExportInvoiceHolder> convertFromDb(InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
var exportList = new List<ExportInvoiceHolder>();
|
var exportList = new List<ExportInvoiceHolder>();
|
||||||
@@ -43,14 +53,15 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
|
|
||||||
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), null);
|
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), null);
|
||||||
var accounting = pmethod.Calculate();
|
var accounting = pmethod.Calculate();
|
||||||
var onchainDetails = pmethod.GetPaymentMethodDetails() as BitcoinLikeOnChainPaymentMethod;
|
var details = pmethod.GetPaymentMethodDetails();
|
||||||
|
|
||||||
var target = new ExportInvoiceHolder
|
var target = new ExportInvoiceHolder
|
||||||
{
|
{
|
||||||
PaymentId = pdata.GetPaymentId(),
|
PaymentId = pdata.GetPaymentId(),
|
||||||
CryptoCode = cryptoCode,
|
CryptoCode = cryptoCode,
|
||||||
ConversionRate = pmethod.Rate,
|
ConversionRate = pmethod.Rate,
|
||||||
Address = onchainDetails?.DepositAddress,
|
PaymentType = details.GetPaymentType().ToString(),
|
||||||
|
Destination = details.GetPaymentDestination(),
|
||||||
PaymentDue = $"{accounting.MinimumTotalDue} {cryptoCode}",
|
PaymentDue = $"{accounting.MinimumTotalDue} {cryptoCode}",
|
||||||
PaymentPaid = $"{accounting.CryptoPaid} {cryptoCode}",
|
PaymentPaid = $"{accounting.CryptoPaid} {cryptoCode}",
|
||||||
PaymentOverpaid = $"{accounting.OverpaidHelper} {cryptoCode}",
|
PaymentOverpaid = $"{accounting.OverpaidHelper} {cryptoCode}",
|
||||||
@@ -78,7 +89,8 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
public string PaymentId { get; set; }
|
public string PaymentId { get; set; }
|
||||||
public string CryptoCode { get; set; }
|
public string CryptoCode { get; set; }
|
||||||
public decimal ConversionRate { 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 PaymentDue { get; set; }
|
||||||
public string PaymentPaid { get; set; }
|
public string PaymentPaid { get; set; }
|
||||||
public string PaymentOverpaid { get; set; }
|
public string PaymentOverpaid { get; set; }
|
||||||
@@ -48,7 +48,8 @@
|
|||||||
Export
|
Export
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
|
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
|
||||||
<a asp-action="Export" asp-route-format="json" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item" target="_blank">CSV</a>
|
<a asp-action="Export" asp-route-format="csv" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item" target="_blank">CSV</a>
|
||||||
|
<a asp-action="Export" asp-route-format="json" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item" target="_blank">JSON</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user