mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Export of payments made on invoices
This commit is contained in:
@@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers
|
|||||||
MonitoringDate = invoice.MonitoringExpiration,
|
MonitoringDate = invoice.MonitoringExpiration,
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.OrderId,
|
||||||
BuyerInformation = invoice.BuyerInformation,
|
BuyerInformation = invoice.BuyerInformation,
|
||||||
Fiat = _CurrencyNameTable.DisplayFormatCurrency((decimal)dto.Price, dto.Currency),
|
Fiat = _CurrencyNameTable.DisplayFormatCurrency(dto.Price, dto.Currency),
|
||||||
NotificationEmail = invoice.NotificationEmail,
|
NotificationEmail = invoice.NotificationEmail,
|
||||||
NotificationUrl = invoice.NotificationURL,
|
NotificationUrl = invoice.NotificationURL,
|
||||||
RedirectUrl = invoice.RedirectURL,
|
RedirectUrl = invoice.RedirectURL,
|
||||||
@@ -74,9 +74,9 @@ namespace BTCPayServer.Controllers
|
|||||||
var paymentMethodId = data.GetId();
|
var paymentMethodId = data.GetId();
|
||||||
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
|
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
|
||||||
cryptoPayment.PaymentMethod = ToString(paymentMethodId);
|
cryptoPayment.PaymentMethod = ToString(paymentMethodId);
|
||||||
cryptoPayment.Due = accounting.Due.ToString() + $" {paymentMethodId.CryptoCode}";
|
cryptoPayment.Due = $"{accounting.Due} {paymentMethodId.CryptoCode}";
|
||||||
cryptoPayment.Paid = accounting.CryptoPaid.ToString() + $" {paymentMethodId.CryptoCode}";
|
cryptoPayment.Paid = $"{accounting.CryptoPaid} {paymentMethodId.CryptoCode}";
|
||||||
cryptoPayment.Overpaid = (accounting.DueUncapped > Money.Zero ? Money.Zero : -accounting.DueUncapped).ToString() + $" {paymentMethodId.CryptoCode}";
|
cryptoPayment.Overpaid = $"{accounting.OverpaidHelper} {paymentMethodId.CryptoCode}";
|
||||||
|
|
||||||
var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
||||||
if (onchainMethod != null)
|
if (onchainMethod != null)
|
||||||
@@ -469,6 +469,20 @@ namespace BTCPayServer.Controllers
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||||
|
[BitpayAPIConstraint(false)]
|
||||||
|
public async Task<IActionResult> Export(string format, string searchTerm = null)
|
||||||
|
{
|
||||||
|
var model = new ExportInvoicesModel();
|
||||||
|
|
||||||
|
var invoices = await ListInvoicesProcess(searchTerm, 0, int.MaxValue);
|
||||||
|
var res = model.Process(invoices, format);
|
||||||
|
return Content(res, "application/" + format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("invoices/create")]
|
[Route("invoices/create")]
|
||||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||||
|
|||||||
96
BTCPayServer/Models/InvoicingModels/ExportInvoicesModel.cs
Normal file
96
BTCPayServer/Models/InvoicingModels/ExportInvoicesModel.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Models.InvoicingModels
|
||||||
|
{
|
||||||
|
public class ExportInvoicesModel
|
||||||
|
{
|
||||||
|
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>();
|
||||||
|
foreach (var i in invoices)
|
||||||
|
{
|
||||||
|
csvInvoices.AddRange(convertFromDb(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
var serializerSett = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
|
||||||
|
var json = JsonConvert.SerializeObject(csvInvoices, Formatting.Indented, serializerSett);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<ExportInvoiceHolder> convertFromDb(InvoiceEntity invoice)
|
||||||
|
{
|
||||||
|
var exportList = new List<ExportInvoiceHolder>();
|
||||||
|
// in this first version we are only exporting invoices that were paid
|
||||||
|
foreach (var payment in invoice.GetPayments())
|
||||||
|
{
|
||||||
|
var cryptoCode = payment.GetPaymentMethodId().CryptoCode;
|
||||||
|
var pdata = payment.GetCryptoPaymentData();
|
||||||
|
|
||||||
|
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), null);
|
||||||
|
var accounting = pmethod.Calculate();
|
||||||
|
var onchainDetails = pmethod.GetPaymentMethodDetails() as BitcoinLikeOnChainPaymentMethod;
|
||||||
|
|
||||||
|
var target = new ExportInvoiceHolder
|
||||||
|
{
|
||||||
|
PaymentId = pdata.GetPaymentId(),
|
||||||
|
CryptoCode = cryptoCode,
|
||||||
|
ConversionRate = pmethod.Rate,
|
||||||
|
Address = onchainDetails?.DepositAddress,
|
||||||
|
PaymentDue = $"{accounting.MinimumTotalDue} {cryptoCode}",
|
||||||
|
PaymentPaid = $"{accounting.CryptoPaid} {cryptoCode}",
|
||||||
|
PaymentOverpaid = $"{accounting.OverpaidHelper} {cryptoCode}",
|
||||||
|
|
||||||
|
InvoiceId = invoice.Id,
|
||||||
|
CreatedDate = invoice.InvoiceTime.UtcDateTime,
|
||||||
|
ExpirationDate = invoice.ExpirationTime.UtcDateTime,
|
||||||
|
MonitoringDate = invoice.MonitoringExpiration.UtcDateTime,
|
||||||
|
Status = invoice.Status,
|
||||||
|
ItemCode = invoice.ProductInformation?.ItemCode,
|
||||||
|
ItemDesc = invoice.ProductInformation?.ItemDesc,
|
||||||
|
FiatPrice = invoice.ProductInformation?.Price ?? 0,
|
||||||
|
FiatCurrency = invoice.ProductInformation?.Currency,
|
||||||
|
};
|
||||||
|
|
||||||
|
exportList.Add(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExportInvoiceHolder
|
||||||
|
{
|
||||||
|
public string PaymentId { get; set; }
|
||||||
|
public string CryptoCode { get; set; }
|
||||||
|
public decimal ConversionRate { get; set; }
|
||||||
|
public string Address { get; set; }
|
||||||
|
public string PaymentDue { get; set; }
|
||||||
|
public string PaymentPaid { get; set; }
|
||||||
|
public string PaymentOverpaid { get; set; }
|
||||||
|
|
||||||
|
public string InvoiceId { get; set; }
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
|
public DateTime ExpirationDate { get; set; }
|
||||||
|
public DateTime MonitoringDate { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
public string ItemCode { get; set; }
|
||||||
|
public string ItemDesc { get; set; }
|
||||||
|
public decimal FiatPrice { get; set; }
|
||||||
|
public string FiatCurrency { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -533,20 +533,21 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public class PaymentMethodAccounting
|
public class PaymentMethodAccounting
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>Total amount of this invoice</summary>
|
||||||
/// Total amount of this invoice
|
|
||||||
/// </summary>
|
|
||||||
public Money TotalDue { get; set; }
|
public Money TotalDue { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Amount of crypto remaining to pay this invoice</summary>
|
||||||
/// Amount of crypto remaining to pay this invoice
|
|
||||||
/// </summary>
|
|
||||||
public Money Due { get; set; }
|
public Money Due { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Same as Due, can be negative</summary>
|
||||||
/// Same as Due, can be negative
|
|
||||||
/// </summary>
|
|
||||||
public Money DueUncapped { get; set; }
|
public Money DueUncapped { get; set; }
|
||||||
|
|
||||||
|
/// <summary>If DueUncapped is negative, that means user overpaid invoice</summary>
|
||||||
|
public Money OverpaidHelper
|
||||||
|
{
|
||||||
|
get { return DueUncapped > Money.Zero ? Money.Zero : -DueUncapped; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total amount of the invoice paid after conversion to this crypto currency
|
/// Total amount of the invoice paid after conversion to this crypto currency
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -41,11 +41,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row no-gutter" style="margin-bottom: 5px;">
|
<div class="row no-gutter" style="margin-bottom: 5px;">
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-6">
|
||||||
<a asp-action="CreateInvoice" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new invoice</a>
|
<a asp-action="CreateInvoice" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new invoice</a>
|
||||||
|
|
||||||
|
<a class="btn btn-primary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
Export
|
||||||
|
</a>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<form asp-action="SearchInvoice" method="post" style="float:right;">
|
<form asp-action="SearchInvoice" method="post" style="float:right;">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
|||||||
Reference in New Issue
Block a user