mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-11 01:35:22 +01:00
Add POS Data to Invoice UI (#409)
* Add POS Data in Invoice UI * fix build * extract in helper and add UTs * add in unit test coverage through mvc view too
This commit is contained in:
parent
c25831316e
commit
e144d2479b
4 changed files with 278 additions and 137 deletions
|
@ -43,6 +43,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using System.Security.Claims;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using NBXplorer.Models;
|
||||
|
@ -79,7 +80,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.False(attribute.IsValid("http://"));
|
||||
Assert.False(attribute.IsValid("httpdsadsa.com"));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanCalculateCryptoDue2()
|
||||
|
@ -1505,6 +1506,81 @@ donation:
|
|||
Assert.Equal("donation", donationInvoice.ItemDesc);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void PosDataParser_ParsesCorrectly()
|
||||
{
|
||||
var testCases =
|
||||
new List<(string input, Dictionary<string, string> expectedOutput)>()
|
||||
{
|
||||
{ (null, new Dictionary<string, string>())},
|
||||
{("", new Dictionary<string, string>())},
|
||||
{("{}", new Dictionary<string, string>())},
|
||||
{("non-json-content", new Dictionary<string, string>(){ {string.Empty, "non-json-content"}})},
|
||||
{("[1,2,3]", new Dictionary<string, string>(){ {string.Empty, "[1,2,3]"}})},
|
||||
{("{ \"key\": \"value\"}", new Dictionary<string, string>(){ {"key", "value"}})},
|
||||
{("{ \"key\": true}", new Dictionary<string, string>(){ {"key", "True"}})},
|
||||
{("{ \"key\": \"value\", \"key2\": [\"value\", \"value2\"]}",
|
||||
new Dictionary<string, string>(){ {"key", "value"}, {"key2", "value,value2"}})},
|
||||
{("{ invalidjson file here}", new Dictionary<string, string>(){ {String.Empty, "{ invalidjson file here}"}})}
|
||||
};
|
||||
|
||||
testCases.ForEach(tuple =>
|
||||
{
|
||||
Assert.Equal(tuple.expectedOutput, InvoiceController.PosDataParser.ParsePosData(tuple.input));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task PosDataParser_ParsesCorrectly_Slower()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var controller = tester.PayTester.GetController<InvoiceController>(null);
|
||||
|
||||
var testCases =
|
||||
new List<(string input, Dictionary<string, string> expectedOutput)>()
|
||||
{
|
||||
{ (null, new Dictionary<string, string>())},
|
||||
{("", new Dictionary<string, string>())},
|
||||
{("{}", new Dictionary<string, string>())},
|
||||
{("non-json-content", new Dictionary<string, string>(){ {string.Empty, "non-json-content"}})},
|
||||
{("[1,2,3]", new Dictionary<string, string>(){ {string.Empty, "[1,2,3]"}})},
|
||||
{("{ \"key\": \"value\"}", new Dictionary<string, string>(){ {"key", "value"}})},
|
||||
{("{ \"key\": true}", new Dictionary<string, string>(){ {"key", "True"}})},
|
||||
{("{ \"key\": \"value\", \"key2\": [\"value\", \"value2\"]}",
|
||||
new Dictionary<string, string>(){ {"key", "value"}, {"key2", "value,value2"}})},
|
||||
{("{ invalidjson file here}", new Dictionary<string, string>(){ {String.Empty, "{ invalidjson file here}"}})}
|
||||
};
|
||||
|
||||
var tasks = new List<Task>();
|
||||
foreach (var valueTuple in testCases)
|
||||
{
|
||||
tasks.Add(user.BitPay.CreateInvoiceAsync(new Invoice(1, "BTC")
|
||||
{
|
||||
PosData = valueTuple.input
|
||||
}).ContinueWith(async task =>
|
||||
{
|
||||
var result = await controller.Invoice(task.Result.Id);
|
||||
var viewModel =
|
||||
Assert.IsType<InvoiceDetailsModel>(
|
||||
Assert.IsType<ViewResult>(result).Model);
|
||||
Assert.Equal(valueTuple.expectedOutput, viewModel.PosData);
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
|
|
|
@ -14,13 +14,13 @@ using BTCPayServer.Payments.Changelly;
|
|||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
|
@ -41,7 +41,6 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
|
||||
InvoiceDetailsModel model = new InvoiceDetailsModel()
|
||||
{
|
||||
StoreName = store.StoreName,
|
||||
|
@ -64,7 +63,8 @@ namespace BTCPayServer.Controllers
|
|||
RedirectUrl = invoice.RedirectURL,
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
StatusException = invoice.ExceptionStatus,
|
||||
Events = invoice.Events
|
||||
Events = invoice.Events,
|
||||
PosData = PosDataParser.ParsePosData(dto.PosData)
|
||||
};
|
||||
|
||||
foreach (var data in invoice.GetPaymentMethods(null))
|
||||
|
@ -586,5 +586,43 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
}
|
||||
|
||||
public class PosDataParser
|
||||
{
|
||||
public static Dictionary<string, string> ParsePosData(string posData)
|
||||
{
|
||||
var result = new Dictionary<string,string>();
|
||||
if (string.IsNullOrEmpty(posData))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
var jObject =JObject.Parse(posData);
|
||||
foreach (var item in jObject)
|
||||
{
|
||||
|
||||
switch (item.Value.Type)
|
||||
{
|
||||
case JTokenType.Array:
|
||||
result.Add(item.Key, string.Join(',', item.Value.AsEnumerable()));
|
||||
break;
|
||||
default:
|
||||
result.Add(item.Key, item.Value.ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
result.Add(string.Empty, posData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,5 +143,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
public DateTimeOffset MonitoringDate { get; internal set; }
|
||||
public List<Data.InvoiceEventData> Events { get; internal set; }
|
||||
public string NotificationEmail { get; internal set; }
|
||||
public Dictionary<string, string> PosData { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,128 +42,177 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>Information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Store</th>
|
||||
<td><a href="@Model.StoreLink">@Model.StoreName</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>@Model.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created date</th>
|
||||
<td>@Model.CreatedDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Expiration date</th>
|
||||
<td>@Model.ExpirationDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Monitoring date</th>
|
||||
<td>@Model.MonitoringDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Transaction speed</th>
|
||||
<td>@Model.TransactionSpeed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>@Model.Status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status Exception</th>
|
||||
<td>@Model.StatusException</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Refund email</th>
|
||||
<td><a href="mailto:@Model.RefundEmail">@Model.RefundEmail</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Order Id</th>
|
||||
<td>@Model.OrderId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total fiat due</th>
|
||||
<td>@Model.Fiat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Notification Email</th>
|
||||
<td>@Model.NotificationEmail</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Notification Url</th>
|
||||
<td>@Model.NotificationUrl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Redirect Url</th>
|
||||
<td><a href="@Model.RedirectUrl">@Model.RedirectUrl</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h3>Buyer information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>@Model.BuyerInformation.BuyerName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td><a href="mailto:@Model.BuyerInformation.BuyerEmail">@Model.BuyerInformation.BuyerEmail</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Phone</th>
|
||||
<td>@Model.BuyerInformation.BuyerPhone</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 1</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 2</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<td>@Model.BuyerInformation.BuyerCity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>State</th>
|
||||
<td>@Model.BuyerInformation.BuyerState</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Country</th>
|
||||
<td>@Model.BuyerInformation.BuyerCountry</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Zip</th>
|
||||
<td>@Model.BuyerInformation.BuyerZip</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Product information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Item code</th>
|
||||
<td>@Model.ProductInformation.ItemCode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Item Description</th>
|
||||
<td>@Model.ProductInformation.ItemDesc</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Price</th>
|
||||
<td>@Model.ProductInformation.Price @Model.ProductInformation.Currency</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>Information</h3>
|
||||
<h3>Pos Data</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Store</th>
|
||||
<td><a href="@Model.StoreLink">@Model.StoreName</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>@Model.Id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created date</th>
|
||||
<td>@Model.CreatedDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Expiration date</th>
|
||||
<td>@Model.ExpirationDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Monitoring date</th>
|
||||
<td>@Model.MonitoringDate.ToBrowserDate()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Transaction speed</th>
|
||||
<td>@Model.TransactionSpeed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>@Model.Status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status Exception</th>
|
||||
<td>@Model.StatusException</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Refund email</th>
|
||||
<td><a href="mailto:@Model.RefundEmail">@Model.RefundEmail</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Order Id</th>
|
||||
<td>@Model.OrderId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total fiat due</th>
|
||||
<td>@Model.Fiat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Notification Email</th>
|
||||
<td>@Model.NotificationEmail</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Notification Url</th>
|
||||
<td>@Model.NotificationUrl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Redirect Url</th>
|
||||
<td><a href="@Model.RedirectUrl">@Model.RedirectUrl</a></td>
|
||||
</tr>
|
||||
@foreach (var posDataItem in Model.PosData)
|
||||
{
|
||||
<tr>
|
||||
@if (!string.IsNullOrEmpty(posDataItem.Key))
|
||||
{
|
||||
|
||||
<th>@posDataItem.Key</th>
|
||||
<td>@posDataItem.Value</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<td colspan="2">@posDataItem.Value</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
<h3>Buyer information</h3>
|
||||
<h3>Addresses</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>@Model.BuyerInformation.BuyerName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td><a href="mailto:@Model.BuyerInformation.BuyerEmail">@Model.BuyerInformation.BuyerEmail</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Phone</th>
|
||||
<td>@Model.BuyerInformation.BuyerPhone</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 1</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address 2</th>
|
||||
<td>@Model.BuyerInformation.BuyerAddress2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<td>@Model.BuyerInformation.BuyerCity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>State</th>
|
||||
<td>@Model.BuyerInformation.BuyerState</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Country</th>
|
||||
<td>@Model.BuyerInformation.BuyerCountry</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Zip</th>
|
||||
<td>@Model.BuyerInformation.BuyerZip</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Product information</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tr>
|
||||
<th>Item code</th>
|
||||
<td>@Model.ProductInformation.ItemCode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Item Description</th>
|
||||
<td>@Model.ProductInformation.ItemDesc</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Price</th>
|
||||
<td>@Model.ProductInformation.Price @Model.ProductInformation.Currency</td>
|
||||
<th class="firstCol">Payment method</th>
|
||||
<th>Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var address in Model.Addresses)
|
||||
{
|
||||
var current = address.Current ? "font-weight-bold" : "";
|
||||
<tr>
|
||||
<td>@address.PaymentMethod</td>
|
||||
<td class="smMaxWidth text-truncate @current">@address.Destination</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Paid summary</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
|
@ -258,29 +307,6 @@
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Addresses</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th class="firstCol">Payment method</th>
|
||||
<th>Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var address in Model.Addresses)
|
||||
{
|
||||
var current = address.Current ? "font-weight-bold" : "";
|
||||
<tr>
|
||||
<td>@address.PaymentMethod</td>
|
||||
<td class="smMaxWidth text-truncate @current">@address.Destination</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
|
Loading…
Add table
Reference in a new issue