Unify BalanceViews reset and topup

This commit is contained in:
nicolas.dorier 2024-04-22 12:16:34 +09:00
parent 335d6f69b5
commit e5a445b383
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
7 changed files with 114 additions and 140 deletions

View file

@ -36,6 +36,17 @@ public static class HttpRequestExtensions
request.Path.ToUriComponent()); request.Path.ToUriComponent());
} }
public static string GetCurrentUrlWithQueryString(this HttpRequest request)
{
return string.Concat(
request.Scheme,
"://",
request.Host.ToUriComponent(),
request.PathBase.ToUriComponent(),
request.Path.ToUriComponent(),
request.QueryString.ToUriComponent());
}
public static string GetCurrentPath(this HttpRequest request) public static string GetCurrentPath(this HttpRequest request)
{ {
return string.Concat( return string.Concat(

View file

@ -2,7 +2,6 @@ using System;
using System.Linq; using System.Linq;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
using BTCPayServer.Controllers.Greenfield; using BTCPayServer.Controllers.Greenfield;
@ -40,7 +39,7 @@ namespace BTCPayServer.Plugins.BoltcardBalance.Controllers
_serializerSettings = serializerSettings; _serializerSettings = serializerSettings;
} }
[HttpGet("boltcards/balance")] [HttpGet("boltcards/balance")]
public async Task<IActionResult> ScanCard([FromQuery] string p = null, [FromQuery] string c = null) public async Task<IActionResult> ScanCard([FromQuery] string p = null, [FromQuery] string c = null, [FromQuery]string view = null)
{ {
if (p is null || c is null) if (p is null || c is null)
{ {
@ -51,7 +50,8 @@ namespace BTCPayServer.Plugins.BoltcardBalance.Controllers
//{ //{
// AmountDue = 10000m, // AmountDue = 10000m,
// Currency = "SATS", // Currency = "SATS",
// Transactions = [new() { Date = DateTimeOffset.UtcNow, Balance = -3.0m }, new() { Date = DateTimeOffset.UtcNow, Balance = -5.0m }] // Transactions = [new() { Date = DateTimeOffset.UtcNow, Balance = -3.0m }, new() { Date = DateTimeOffset.UtcNow, Balance = -5.0m }],
// ViewMode = Mode.Reset
//}); //});
var issuerKey = await _settingsRepository.GetIssuerKey(_env); var issuerKey = await _settingsRepository.GetIssuerKey(_env);
@ -62,10 +62,10 @@ namespace BTCPayServer.Plugins.BoltcardBalance.Controllers
var registration = await _dbContextFactory.GetBoltcardRegistration(issuerKey, boltData, true); var registration = await _dbContextFactory.GetBoltcardRegistration(issuerKey, boltData, true);
if (registration is null) if (registration is null)
return NotFound(); return NotFound();
return await GetBalanceView(registration, p, issuerKey); return await GetBalanceView(registration, p, issuerKey, view);
} }
[NonAction] [NonAction]
public async Task<IActionResult> GetBalanceView(BoltcardDataExtensions.BoltcardRegistration registration, string p, IssuerKey issuerKey) public async Task<IActionResult> GetBalanceView(BoltcardDataExtensions.BoltcardRegistration registration, string p, IssuerKey issuerKey, string view = null)
{ {
var ppId = registration.PullPaymentId; var ppId = registration.PullPaymentId;
var boltCardKeys = issuerKey.CreatePullPaymentCardKey(registration.UId, registration.Version, ppId).DeriveBoltcardKeys(issuerKey); var boltCardKeys = issuerKey.CreatePullPaymentCardKey(registration.UId, registration.Version, ppId).DeriveBoltcardKeys(issuerKey);
@ -124,7 +124,7 @@ namespace BTCPayServer.Plugins.BoltcardBalance.Controllers
Balance = blob.Limit, Balance = blob.Limit,
Status = PayoutState.Completed Status = PayoutState.Completed
}); });
vm.ViewMode = view?.Equals("Reset", StringComparison.OrdinalIgnoreCase) is true ? Mode.Reset : Mode.TopUp;
return View($"{BoltcardBalancePlugin.ViewsDirectory}/BalanceView.cshtml", vm); return View($"{BoltcardBalancePlugin.ViewsDirectory}/BalanceView.cshtml", vm);
} }

View file

@ -4,6 +4,11 @@ using BTCPayServer.Client.Models;
namespace BTCPayServer.Plugins.BoltcardBalance.ViewModels namespace BTCPayServer.Plugins.BoltcardBalance.ViewModels
{ {
public enum Mode
{
TopUp,
Reset
}
public class BalanceViewModel public class BalanceViewModel
{ {
public class Transaction public class Transaction
@ -22,5 +27,6 @@ namespace BTCPayServer.Plugins.BoltcardBalance.ViewModels
public string LNUrlPay { get; set; } public string LNUrlPay { get; set; }
public string WipeData{ get; set; } public string WipeData{ get; set; }
public Mode ViewMode { get; set; }
} }
} }

View file

@ -13,6 +13,12 @@
<nav id="wizard-navbar"> <nav id="wizard-navbar">
@if (this.ViewData["NoCancelWizard"] is not true) @if (this.ViewData["NoCancelWizard"] is not true)
{ {
@if (Model.ViewMode == Mode.TopUp)
{
<button type="button" class="btn btn-secondary only-for-js mt-4" id="lnurlwithdraw-button">
<span class="fa fa-qrcode fa-2x" title="Deposit"></span>
</button>
}
<a href="#" id="CancelWizard" class="cancel mt-4"> <a href="#" id="CancelWizard" class="cancel mt-4">
<vc:icon symbol="close" /> <vc:icon symbol="close" />
</a> </a>
@ -25,48 +31,55 @@
<dt class="h4 fw-semibold text-nowrap text-primary text-print-default order-2 order-sm-1 mb-1">@DisplayFormatter.Currency(Model.AmountDue, Model.Currency)</dt> <dt class="h4 fw-semibold text-nowrap text-primary text-print-default order-2 order-sm-1 mb-1">@DisplayFormatter.Currency(Model.AmountDue, Model.Currency)</dt>
</div> </div>
</dl> </dl>
@if (Model.ViewMode == Mode.TopUp)
{
<div class="lnurl-pay boltcard-details d-none">
<vc:qr-code data="@Model.LNUrlBech32" />
</div>
}
</div> </div>
</div> </div>
@if (Model.AmountDue > 0) @if (Model.ViewMode == Mode.Reset)
{ {
<div class="d-flex justify-content-center"> @if (Model.AmountDue > 0)
<a class="btn btn-outline-primary" href="@Model.PullPaymentLink">Sweep remaining balance</a> {
</div>
}
<div class="d-flex justify-content-center nfc-supported mt-2">
<div class="boltcard-reset boltcard-details text-center">
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
<div class="input-group"> <a class="btn btn-outline-primary" href="@Model.PullPaymentLink">Sweep remaining balance</a>
<a class="btn btn-outline-danger form-control" href="@Model.BoltcardKeysResetLink">
<div style="margin-top:3px">Reset Boltcard</div>
</a>
<button type="button" class="btn btn-outline-danger input-group-btn" id="show-wipe-qr">
<span class="fa fa-qrcode fa-2x" title="Show wipe QR"></span>
</button>
</div>
</div> </div>
<div id="wipe-qr" class="d-none mt-2 cursor-pointer" data-clipboard-target="#qr-wipe-code-data-input"> }
<div class="d-flex"> <div class="d-flex justify-content-center nfc-supported mt-2">
<vc:qr-code data="@Model.WipeData" /> <div class="boltcard-reset boltcard-details text-center">
</div>
<div class="d-flex"> <div class="d-flex justify-content-center">
<div class="input-group input-group-sm mt-3"> <div class="input-group">
<input type="text" class="form-control" readonly value="@Model.WipeData" id="qr-wipe-code-data-input"> <a class="btn btn-outline-danger form-control" href="@Model.BoltcardKeysResetLink">
<button type="button" class="btn btn-outline-secondary px-3" data-clipboard-target="#qr-wipe-code-data-input"> <div style="margin-top:3px">Reset Boltcard</div>
<vc:icon symbol="copy" /> </a>
<button type="button" class="btn btn-outline-danger input-group-btn" id="show-wipe-qr">
<span class="fa fa-qrcode fa-2x" title="Show wipe QR"></span>
</button> </button>
</div> </div>
</div> </div>
<div id="wipe-qr" class="d-none mt-2 cursor-pointer" data-clipboard-target="#qr-wipe-code-data-input">
<div class="d-flex">
<vc:qr-code data="@Model.WipeData" />
</div>
<div class="d-flex">
<div class="input-group input-group-sm mt-3">
<input type="text" class="form-control" readonly value="@Model.WipeData" id="qr-wipe-code-data-input">
<button type="button" class="btn btn-outline-secondary px-3" data-clipboard-target="#qr-wipe-code-data-input">
<vc:icon symbol="copy" />
</button>
</div>
</div>
</div>
<p class="text-secondary mt-2">Requires installing the <a href="https://play.google.com/store/apps/details?id=com.lightningnfcapp&hl=en&gl=US">Bolt Card Creator app</a></p>
</div> </div>
<p class="text-secondary mt-2">Requires installing the <a href="https://play.google.com/store/apps/details?id=com.lightningnfcapp&hl=en&gl=US">Bolt Card Creator app</a></p>
</div> </div>
</div> }
</div> </div>
</div> </div>
@ -76,21 +89,21 @@
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded table-responsive"> <div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded table-responsive">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th class="date-col">Date</th> <th class="date-col">Date</th>
<th class="amount-col">Amount</th> <th class="amount-col">Amount</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var tx in Model.Transactions) @foreach (var tx in Model.Transactions)
{ {
<tr> <tr>
<td class="date-col">@tx.Date.ToBrowserDate(ViewsRazor.DateDisplayFormat.Relative)</td> <td class="date-col">@tx.Date.ToBrowserDate(ViewsRazor.DateDisplayFormat.Relative)</td>
<td class="amount-col"> <td class="amount-col">
<span data-sensitive class="text-@(tx.Positive ? "success" : "danger")">@DisplayFormatter.Currency(tx.Balance, Model.Currency, DisplayFormatter.CurrencyFormat.Code)</span> <span data-sensitive class="text-@(tx.Positive ? "success" : "danger")">@DisplayFormatter.Currency(tx.Balance, Model.Currency, DisplayFormatter.CurrencyFormat.Code)</span>
</td> </td>
</tr> </tr>
} }
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -1,73 +0,0 @@
@using BTCPayServer.Plugins.BoltcardBalance.ViewModels
@using BTCPayServer.Services
@inject DisplayFormatter DisplayFormatter
@model BalanceViewModel
@{
Layout = null;
}
<div class="col col-12 col-lg-12 mb-4">
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
<nav id="wizard-navbar">
@if (this.ViewData["NoCancelWizard"] is not true)
{
<button type="button" class="btn btn-secondary only-for-js mt-4" id="lnurlwithdraw-button">
<span class="fa fa-qrcode fa-2x" title="Deposit"></span>
</button>
<button type="button" class="btn btn-outline-danger only-for-js mt-4" id="reset-button">
<span>Reset Boltcard</span>
</button>
<a href="#" id="CancelWizard" class="cancel mt-4">
<vc:icon symbol="close" />
</a>
}
</nav>
<div class="d-flex justify-content-center">
<div class="d-flex flex-column justify-content-center align-items-center">
<dl class="mb-0 mt-md-4">
<div class="d-flex d-print-inline-block flex-column mb-4">
<dt class="h4 fw-semibold text-nowrap text-primary text-print-default order-2 order-sm-1 mb-1">@DisplayFormatter.Currency(Model.AmountDue, Model.Currency)</dt>
</div>
</dl>
<div class="lnurl-pay boltcard-details d-none">
<vc:qr-code data="@Model.LNUrlBech32" />
</div>
<div class="boltcard-reset boltcard-details d-flex gap-3 mt-3 mt-sm-0 d-none">
<a class="btn btn-outline-danger" target="_blank" href="@Model.BoltcardKeysResetLink">Reset Boltcard from app</a>
</div>
<div class="lnurl-pay boltcard-details d-flex gap-3 mt-3 mt-sm-0 d-none">
<a class="btn btn-primary" target="_blank" href="@Model.LNUrlBech32">Deposit from Wallet...</a>
</div>
</div>
</div>
</div>
</div>
@if (Model.Transactions.Any())
{
<div class="col col-12 col-lg-12 mb-4">
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th class="date-col">Date</th>
<th class="amount-col">Amount</th>
</tr>
</thead>
<tbody>
@foreach (var tx in Model.Transactions)
{
<tr>
<td class="date-col">@tx.Date.ToBrowserDate(ViewsRazor.DateDisplayFormat.Relative)</td>
<td class="amount-col">
<span data-sensitive class="text-@(tx.Positive ? "success" : "danger")">@DisplayFormatter.Currency(tx.Balance, Model.Currency, DisplayFormatter.CurrencyFormat.Code)</span>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}

View file

@ -11,4 +11,8 @@
<vc:icon symbol="pay-button" /> <vc:icon symbol="pay-button" />
<span>Boltcard Balance</span> <span>Boltcard Balance</span>
</a> </a>
<a asp-area="" asp-controller="UIBoltcardBalance" asp-action="ScanCard" asp-route-view="Reset" class="nav-link">
<vc:icon symbol="pay-button" />
<span>Boltcard reset</span>
</a>
</li> </li>

View file

@ -32,7 +32,7 @@
<div id="qr" class="d-flex flex-column align-items-center justify-content-center d-none"> <div id="qr" class="d-flex flex-column align-items-center justify-content-center d-none">
<div class="d-inline-flex flex-column" style="width:256px"> <div class="d-inline-flex flex-column" style="width:256px">
<div class="qr-container mb-2"> <div class="qr-container mb-2">
<vc:qr-code data="@Context.Request.GetCurrentUrl()" /> <vc:qr-code data="@Context.Request.GetCurrentUrlWithQueryString()" />
</div> </div>
</div> </div>
<p class="text-secondary">NFC not supported in this device</p> <p class="text-secondary">NFC not supported in this device</p>
@ -68,20 +68,25 @@
document.getElementById("error").classList.add("d-none"); document.getElementById("error").classList.add("d-none");
} }
} }
function toggleDetails(className)
function toggleDetailsWhenPressed(buttonId, className)
{ {
var el = document.getElementsByClassName("boltcard-details"); var button = document.getElementById(buttonId);
for (var i = 0; i < el.length; i++) { if (button) {
if (el[i].classList.contains(className)) { var el = document.getElementsByClassName("boltcard-details");
if (el[i].classList.contains("d-none")) button.addEventListener("click", function () {
el[i].classList.remove("d-none"); for (var i = 0; i < el.length; i++) {
else if (el[i].classList.contains(className)) {
el[i].classList.add("d-none"); if (el[i].classList.contains("d-none"))
} el[i].classList.remove("d-none");
else else
{ el[i].classList.add("d-none");
el[i].classList.add("d-none"); }
} else {
el[i].classList.add("d-none");
}
}
});
} }
} }
@ -96,6 +101,11 @@
var url = window.location.href.replace("#", ""); var url = window.location.href.replace("#", "");
url = url.split("?")[0] + "?" + lnurlw.split("?")[1]; url = url.split("?")[0] + "?" + lnurlw.split("?")[1];
// url = "https://testnet.demo.btcpayserver.org/boltcards/balance?p=...&c=..." // url = "https://testnet.demo.btcpayserver.org/boltcards/balance?p=...&c=..."
var params = new URLSearchParams(window.location.search);
if (params.toString()) {
url += "&" + params;
}
var xhttp = new XMLHttpRequest(); var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = async function () { xhttp.onreadystatechange = async function () {
@ -106,13 +116,16 @@
setState(initState); setState(initState);
document.getElementById("balance-table").innerHTML = ""; document.getElementById("balance-table").innerHTML = "";
}); });
toggleDetailsWhenPressed('lnurlwithdraw-button', 'lnurl-pay');
toggleDetailsWhenPressed('reset-button', 'boltcard-reset');
await uiDelay; await uiDelay;
setState("ShowBalance"); setState("ShowBalance");
} }
else if(this.readyState == 4 && this.status == 404) { else if(this.readyState == 4 && this.status == 404) {
setState(initState); setState(initState);
handleError(new Error("Initialized, but NOT by Bitcoin Atlantis")); handleError(new Error("Initialized by a different provider"));
} }
else { else {
setState(initState); setState(initState);
@ -233,7 +246,7 @@
catch (e) { catch (e) {
handleError(e); handleError(e);
} }
// showBalance("lnurl://ewfw?p=test&c=test"); // showBalance("lnurl://ewfw?p=test&c=test");
}); });
})(); })();
</script> </script>