mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-01-20 22:44:27 +01:00
Based on the `ur-registry` upgrade I refactored the `CameraScanner` and `ShowQR` partials: Besides general code changes, the main change is that most of the configuration and result handling now happens on the outer view. Those partials and functions are now generalized and don't know about their purpose (like handling PSBTs): They can be instantiated with simple data (e.g. for displaying a plain QR code) or different modes (like showing a static and the UR version of a QR code) and the result handling is done via callback. The callbacks can now also distinguish between the different results (data as plain string vs. UR-type objects for wallet data or PSBT) and also handle the specific type of data. For instance: Before it wasn't possible to strip the leading derivation path from an xpub when scanning the QR code, because the scanner didn't know about the type of data it was handling. Now that the data is handled in the callback, we can implement that functionality for the scan view only.
264 lines
14 KiB
Plaintext
264 lines
14 KiB
Plaintext
@inject BTCPayServer.Services.BTCPayServerEnvironment Env
|
|
@inject BTCPayServer.Services.ThemeSettings Theme
|
|
@inject BTCPayNetworkProvider BtcPayNetworkProvider
|
|
@using NUglify.Helpers
|
|
@using BTCPayServer.Client
|
|
@using BTCPayServer.Components.ThemeSwitch
|
|
@using BTCPayServer.Payments
|
|
@model BTCPayServer.Models.ViewPullPaymentModel
|
|
|
|
@{
|
|
ViewData["Title"] = Model.Title;
|
|
Layout = null;
|
|
string StatusTextClass(string status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case "Completed":
|
|
case "In Progress":
|
|
return "bg-success";
|
|
case "Cancelled":
|
|
return "bg-danger";
|
|
default:
|
|
return "bg-warning";
|
|
}
|
|
}
|
|
|
|
|
|
string lnurl = null;
|
|
string lnurlUri = null;
|
|
|
|
var pms = Model.PaymentMethods.FirstOrDefault(id => id.PaymentType == LightningPaymentType.Instance && BtcPayNetworkProvider.DefaultNetwork.CryptoCode == id.CryptoCode);
|
|
if (pms is not null && Model.Currency.Equals(pms.CryptoCode, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
var lnurlEndpoint = new Uri(Url.Action("GetLNURLForPullPayment", "UILNURL", new
|
|
{
|
|
cryptoCode = pms.CryptoCode,
|
|
pullPaymentId = Model.Id
|
|
}, Context.Request.Scheme, Context.Request.Host.ToString()));
|
|
lnurl = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", true).ToString();
|
|
lnurlUri = LNURL.LNURL.EncodeUri(lnurlEndpoint, "withdrawRequest", false).ToString();
|
|
}
|
|
}
|
|
<!DOCTYPE html>
|
|
<html lang="en" @(Env.IsDeveloping ? " data-devenv" : "")>
|
|
<head>
|
|
<partial name="LayoutHead" />
|
|
<bundle name="wwwroot/bundles/payment-request-bundle.min.css" asp-append-version="true"></bundle>
|
|
@if (Model.CustomCSSLink != null)
|
|
{
|
|
<link href="@Model.CustomCSSLink" rel="stylesheet" />
|
|
}
|
|
@Safe.Raw(Model.EmbeddedCSS)
|
|
<style>
|
|
.no-marker > ul { list-style-type: none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="min-vh-100 d-flex flex-column">
|
|
@if (Model.IsPending)
|
|
{
|
|
<nav class="btcpay-header navbar sticky-top py-3 py-lg-4 d-print-none">
|
|
<div class="container gap-3">
|
|
<form asp-action="ClaimPullPayment" asp-route-pullPaymentId="@Model.Id" class="flex-fill">
|
|
<div class="row align-items-center" style="width:calc(100% + 30px)">
|
|
<div class="col-12 mb-3 col-lg-6 mb-lg-0">
|
|
<div class="input-group">
|
|
@if (lnurl is not null)
|
|
{
|
|
<button type="button" class="input-group-prepend btn btn-outline-secondary" id="lnurlwithdraw-button" data-bs-toggle="modal" data-bs-target="#scan-qr-modal">
|
|
<span class="fa fa-qrcode fa-2x" title="LNURL-Withdraw"></span>
|
|
</button>
|
|
}
|
|
<input class="form-control form-control-lg font-monospace" asp-for="Destination" placeholder="Enter destination to claim funds" required style="font-size:.9rem;height:42px;">
|
|
@if (Model.PaymentMethods.Length == 1)
|
|
{
|
|
<input type="hidden" asp-for="SelectedPaymentMethod">
|
|
<span class="input-group-text">@Model.PaymentMethods.First().ToPrettyString()</span>
|
|
}
|
|
else
|
|
{
|
|
<select class="form-select w-auto" asp-for="SelectedPaymentMethod" asp-items="Model.PaymentMethods.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString()))"></select>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 mb-3 col-sm-6 mb-sm-0 col-lg-3">
|
|
<div class="input-group">
|
|
<input type="number" inputmode="decimal" class="form-control form-control-lg text-end hide-number-spin" asp-for="ClaimedAmount" max="@Model.AmountDue" min="@Model.MinimumClaim" step="any" placeholder="Amount" required>
|
|
<span class="input-group-text px-3">@Model.Currency.ToUpper()</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-sm-6 col-lg-3">
|
|
<button class="btn btn-lg btn-primary w-100 text-nowrap" type="submit">Claim Funds</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</nav>
|
|
}
|
|
|
|
<main class="flex-grow-1 py-4">
|
|
<div class="container">
|
|
<partial name="_StatusMessage" model="@(new ViewDataDictionary(ViewData){ { "Margin", "mb-4" } })" />
|
|
@if (!ViewContext.ModelState.IsValid)
|
|
{
|
|
@Html.ValidationSummary(string.Empty, new { @class = $"alert alert-danger mb-4 pb-0 {(ViewContext.ModelState.ErrorCount.Equals(1) ? "no-marker" : "")}" })
|
|
}
|
|
|
|
<div class="row">
|
|
<div class="col col-12 col-lg-6 mb-4">
|
|
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
|
|
@if (!Model.Title.IsNullOrWhiteSpace())
|
|
{
|
|
<h2 class="h4 mb-3">@Model.Title</h2>
|
|
}
|
|
<div class="d-flex align-items-center">
|
|
<span class="text-muted text-nowrap">Start Date</span>
|
|
|
|
<span class="text-nowrap">@Model.StartDate.ToString("g")</span>
|
|
</div>
|
|
<div class="d-flex align-items-center">
|
|
<span class="text-muted text-nowrap">Last Updated</span>
|
|
|
|
<span class="text-nowrap">@Model.LastRefreshed.ToString("g")</span>
|
|
<button type="button" class="btn btn-link fw-semibold d-none d-lg-inline-block d-print-none border-0 p-0 ms-4 only-for-js" id="copyLink">
|
|
Copy Link
|
|
</button>
|
|
</div>
|
|
@if (!string.IsNullOrEmpty(Model.ResetIn))
|
|
{
|
|
<p>
|
|
<span class="text-muted text-nowrap">Reset in</span>
|
|
|
|
<span class="text-nowrap">@Model.ResetIn</span>
|
|
</p>
|
|
}
|
|
@if (!string.IsNullOrEmpty(Model.Description))
|
|
{
|
|
<div class="mt-4">@Safe.Raw(Model.Description)</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="col col-12 col-lg-6 mb-4">
|
|
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
|
|
<h2 class="h4 mb-3">Payment Details</h2>
|
|
<dl class="mb-0 mt-md-4">
|
|
<div class="d-flex d-print-inline-block flex-column mb-4 me-5">
|
|
<dt class="h4 fw-semibold text-nowrap text-primary text-print-default order-2 order-sm-1 mb-1">@Model.AmountDueFormatted</dt>
|
|
<dd class="order-1 order-sm-2 mb-1">Available claim</dd>
|
|
</div>
|
|
<div class="progress bg-light d-none d-sm-flex mb-sm-4 d-print-none" style="height:5px">
|
|
<div class="progress-bar bg-primary" role="progressbar" style="width:@((Model.AmountCollected / Model.Amount) * 100)%"></div>
|
|
</div>
|
|
<div class="d-flex d-print-inline-block flex-column mb-4 me-5 d-sm-inline-flex mb-sm-0">
|
|
<dt class="h4 fw-semibold text-nowrap order-2 order-sm-1 mb-1">@Model.AmountCollectedFormatted</dt>
|
|
<dd class="order-1 order-sm-2 mb-1">Already claimed</dd>
|
|
</div>
|
|
<div class="d-flex d-print-inline-block flex-column mb-0 d-sm-inline-flex float-sm-end">
|
|
<dt class="h4 text-sm-end fw-semibold text-nowrap order-2 order-sm-1 mb-1">@Model.AmountFormatted</dt>
|
|
<dd class="text-sm-end order-1 order-sm-2 mb-1">Claim limit</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
|
|
<h2 class="h4 mb-0">Claims</h2>
|
|
@if (Model.Payouts.Any())
|
|
{
|
|
<div class="table-responsive">
|
|
<table class="table my-0">
|
|
<thead>
|
|
<tr class="table-borderless">
|
|
<th class="fw-normal text-secondary" scope="col">Destination</th>
|
|
<th class="fw-normal text-secondary" scope="col">Method</th>
|
|
<th class="fw-normal text-secondary text-end text-nowrap">Amount requested</th>
|
|
<th class="fw-normal text-secondary text-end">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var invoice in Model.Payouts)
|
|
{
|
|
<tr>
|
|
<td class="text-break">
|
|
@invoice.Destination
|
|
</td>
|
|
<td class="text-nowrap">@invoice.PaymentMethod.ToPrettyString()</td>
|
|
<td class="text-end text-nowrap">@invoice.AmountFormatted</td>
|
|
<td class="text-end text-nowrap">
|
|
@if (!string.IsNullOrEmpty(invoice.Link))
|
|
{
|
|
<a class="transaction-link text-white badge @StatusTextClass(invoice.Status.ToString())" href="@invoice.Link" rel="noreferrer noopener">@invoice.Status.GetStateString()</a>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-white badge @StatusTextClass(invoice.Status.ToString())">@invoice.Status.GetStateString()</span>
|
|
}
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<p class="text-muted mt-3 mb-0">No claim made yet.</p>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
<footer class="pt-2 pb-4 d-print-none">
|
|
<p class="container text-center" permission="@Policies.CanViewStoreSettings">
|
|
<a asp-action="EditPullPayment"
|
|
asp-controller="UIPullPayment"
|
|
asp-route-storeId="@Model.StoreId"
|
|
asp-route-pullPaymentId="@Model.Id">
|
|
Edit pull payment
|
|
</a>
|
|
</p>
|
|
<div class="container d-flex flex-wrap align-items-center justify-content-center">
|
|
<span class="text-muted mx-2">
|
|
Powered by <a href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">BTCPay Server</a>
|
|
</span>
|
|
@if (!Theme.CustomTheme)
|
|
{
|
|
<vc:theme-switch css-class="text-muted mx-2" responsive="none"/>
|
|
}
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
<partial name="LayoutFoot" />
|
|
@if (lnurl is not null)
|
|
{
|
|
var note = "You can scan or open this link with a <a href='https://github.com/fiatjaf/lnurl-rfc#lnurl-documents' target='_blank' rel='noreferrer noopener'>LNURL-Withdraw</a> enabled wallet.";
|
|
if (!Model.AutoApprove)
|
|
{
|
|
note += "<br/><span class='fw-bold'>Please note that this pull payment does not automatically send out funds, and so will process payment after LNURL-withdraw flow is completed.</span>";
|
|
}
|
|
<script src="~/vendor/vue-qrcode/vue-qrcode.min.js" asp-append-version="true"></script>
|
|
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
|
<partial name="ShowQR"/>
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
const modes = {
|
|
uri: { title: "URI", fragments: [@Safe.Json(lnurlUri)], showData: true, href: @Safe.Json(lnurlUri) },
|
|
bech32: { title: "Bech32", fragments: [@Safe.Json(lnurl)], showData: true, href: @Safe.Json(lnurl) }
|
|
};
|
|
initQRShow({ title: "LNURL Withdraw", note: @Safe.Json(note), modes })
|
|
});
|
|
</script>
|
|
}
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
document.getElementById("copyLink").addEventListener("click", window.copyUrlToClipboard);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|