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:
Andrew Camilleri 2018-11-27 07:13:09 +01:00 committed by Nicolas Dorier
parent c25831316e
commit e144d2479b
4 changed files with 278 additions and 137 deletions

View file

@ -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")]

View file

@ -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;
}
}
}
}

View file

@ -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; }
}
}

View file

@ -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">