mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-01-16 20:44:20 +01:00
Fixes a regression introduced in #6316: Moving the `checkout-payment-method` integration point outside the div above broke the UI for the SiodeShift, FixedFloat and Trocador plugins. Also updated the URL change handler, so that it works for all plugins.
319 lines
20 KiB
Plaintext
319 lines
20 KiB
Plaintext
@using BTCPayServer.Services
|
|
@using BTCPayServer.Abstractions.Contracts
|
|
@inject LanguageService LangService
|
|
@inject BTCPayServerEnvironment Env
|
|
@inject IEnumerable<IUIExtension> UiExtensions
|
|
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
|
|
@model CheckoutModel
|
|
@{
|
|
Layout = null;
|
|
ViewData["Title"] = Model.HtmlTitle;
|
|
ViewData["StoreBranding"] = Model.StoreBranding;
|
|
Csp.UnsafeEval();
|
|
var hasPaymentPlugins = UiExtensions.Any(extension => extension.Location == "checkout-payment-method");
|
|
}
|
|
@functions {
|
|
private string ToJsValue(object value)
|
|
{
|
|
return Safe.Json(value?.ToString()).ToString()?.Replace("\"", "'");
|
|
}
|
|
}
|
|
<!DOCTYPE html>
|
|
<html lang="@Model.DefaultLang" class="@(Model.IsModal ? "checkout-modal" : "")"@(Env.IsDeveloping ? " data-devenv" : "")>
|
|
<head>
|
|
<partial name="LayoutHead"/>
|
|
<meta name="robots" content="noindex,nofollow">
|
|
<link href="~/checkout/checkout.css" asp-append-version="true" rel="stylesheet" />
|
|
@if (!string.IsNullOrEmpty(Model.PaymentSoundUrl))
|
|
{
|
|
<link rel="preload" href="@Model.PaymentSoundUrl" as="audio" />
|
|
}
|
|
@if (!string.IsNullOrEmpty(Model.NfcReadSoundUrl))
|
|
{
|
|
<link rel="preload" href="@Model.NfcReadSoundUrl" as="audio" />
|
|
}
|
|
@if (!string.IsNullOrEmpty(Model.ErrorSoundUrl))
|
|
{
|
|
<link rel="preload" href="@Model.ErrorSoundUrl" as="audio" />
|
|
}
|
|
</head>
|
|
<body class="min-vh-100">
|
|
<div id="Checkout" class="public-page-wrap" v-cloak>
|
|
@if (Model.ShowStoreHeader)
|
|
{
|
|
<partial name="_StoreHeader" model="(Model.StoreName, Model.StoreBranding)" />
|
|
}
|
|
<main class="tile">
|
|
<nav v-if="isModal">
|
|
<button type="button" v-if="isModal" id="close" v-on:click="close">
|
|
<vc:icon symbol="close"/>
|
|
</button>
|
|
</nav>
|
|
<section id="payment" v-if="isActive">
|
|
<div v-if="srvModel.itemDesc && srvModel.itemDesc !== srvModel.storeName" v-text="srvModel.itemDesc" class="fw-semibold text-center text-break text-muted mb-3"></div>
|
|
<div class="d-flex justify-content-center mt-1 text-center">
|
|
@if (Model.IsUnsetTopUp)
|
|
{
|
|
<h2 id="AmountDue" v-t="'any_amount'"></h2>
|
|
}
|
|
else
|
|
{
|
|
<h2 id="AmountDue" v-text="`${srvModel.due} ${srvModel.paymentMethodCurrency}`" :data-clipboard="asNumber(srvModel.due)" data-clipboard-hover :data-amount-due="srvModel.due">@Model.Due @Model.PaymentMethodCurrency</h2>
|
|
}
|
|
</div>
|
|
<div id="PaymentInfo" class="info mt-3 mb-2" v-collapsible="showInfo">
|
|
<div>
|
|
<div class="timer" v-if="showTimer">
|
|
<span class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden"></span></span>
|
|
<span v-t="'expiry_info'"></span> <span class="expiryTime">{{timeText}}</span>
|
|
</div>
|
|
<div class="payment-due" v-if="showPaymentDueInfo">
|
|
<vc:icon symbol="info" />
|
|
<span v-t="'partial_payment_info'"></span>
|
|
</div>
|
|
<div v-if="showPaymentDueInfo" v-html="replaceNewlines($t('still_due', { amount: `${srvModel.due} ${srvModel.paymentMethodCurrency}` }))"></div>
|
|
</div>
|
|
</div>
|
|
<button id="DetailsToggle" class="d-flex align-items-center gap-1 btn btn-link payment-details-button mb-2" type="button" :aria-expanded="displayPaymentDetails ? 'true' : 'false'" v-on:click="displayPaymentDetails = !displayPaymentDetails">
|
|
<span class="fw-semibold" v-t="'view_details'"></span>
|
|
<vc:icon symbol="caret-down" />
|
|
</button>
|
|
<div id="PaymentDetails" class="payment-details" v-collapsible="displayPaymentDetails">
|
|
<payment-details
|
|
:srv-model="srvModel"
|
|
:is-active="isActive"
|
|
:order-amount="orderAmount"
|
|
:paid="paid"
|
|
:due="due"
|
|
:show-recommended-fee="showRecommendedFee"
|
|
class="pb-4" />
|
|
</div>
|
|
<div v-if="displayedPaymentMethods.length > 1 || @Safe.Json(hasPaymentPlugins)" class="mt-3 mb-2">
|
|
<h6 class="text-center mb-3" v-t="'pay_with'"></h6>
|
|
<div class="btcpay-pills d-flex flex-wrap align-items-center justify-content-center gap-2 pb-2">
|
|
<a v-for="crypto in displayedPaymentMethods"
|
|
asp-action="Checkout" asp-route-invoiceId="@Model.InvoiceId"
|
|
class="btcpay-pill m-0 payment-method"
|
|
:class="{ active: pmId === crypto.paymentMethodId }"
|
|
v-on:click.prevent="changePaymentMethod(crypto.paymentMethodId)"
|
|
v-text="crypto.paymentMethodName">
|
|
</a>
|
|
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-payment-method", model = Model })
|
|
</div>
|
|
</div>
|
|
<component v-if="paymentMethodComponent" :is="paymentMethodComponent"
|
|
:model="srvModel"
|
|
:nfc-scanning="nfc.scanning"
|
|
:nfc-supported="nfc.supported"
|
|
:nfc-error-message="nfc.errorMessage"
|
|
:nfc-warning-message="nfc.warningMessage"
|
|
v-on:start-nfc-scan="startNFCScan"
|
|
v-on:handle-nfc-data="handleNFCData"
|
|
v-on:handle-nfc-error="handleNFCError"
|
|
v-on:handle-nfc-result="handleNFCResult" />
|
|
</section>
|
|
<section id="result" v-else>
|
|
<div v-if="isProcessing" id="processing" key="processing">
|
|
<div class="top">
|
|
<span class="icn">
|
|
<div id="confetti" v-if="srvModel.celebratePayment" v-on:click="celebratePayment(5000)"></div>
|
|
<vc:icon symbol="checkout-sent" />
|
|
</span>
|
|
<h4 v-t="'payment_received'" class="mb-4"></h4>
|
|
<p class="text-center" v-t="'payment_received_body'"></p>
|
|
<p class="text-center" v-if="srvModel.receivedConfirmations !== null && srvModel.requiredConfirmations" v-t="{ path: 'payment_received_confirmations', args: { cryptoCode: realPaymentMethodCurrency, receivedConfirmations: srvModel.receivedConfirmations, requiredConfirmations: srvModel.requiredConfirmations } }"></p>
|
|
<div id="PaymentDetails" class="payment-details">
|
|
<dl class="mb-0">
|
|
<div>
|
|
<dt v-t="'invoice_id'"></dt>
|
|
<dd v-text="srvModel.invoiceId" :data-clipboard="srvModel.invoiceId" :data-clipboard-confirm="$t('copy_confirm')"></dd>
|
|
</div>
|
|
<div v-if="srvModel.orderId">
|
|
<dt v-t="'order_id'"></dt>
|
|
<dd v-text="srvModel.orderId" :data-clipboard="srvModel.orderId" :data-clipboard-confirm="$t('copy_confirm')"></dd>
|
|
</div>
|
|
</dl>
|
|
<payment-details
|
|
:srv-model="srvModel"
|
|
:is-active="isActive"
|
|
:order-amount="orderAmount"
|
|
:paid="paid"
|
|
:due="due"
|
|
:show-recommended-fee="showRecommendedFee"
|
|
v-collapsible="displayPaymentDetails" />
|
|
</div>
|
|
<button class="d-flex align-items-center gap-1 btn btn-link payment-details-button" type="button" :aria-expanded="displayPaymentDetails ? 'true' : 'false'" v-on:click="displayPaymentDetails = !displayPaymentDetails">
|
|
<span class="fw-semibold" v-t="'view_details'"></span>
|
|
<vc:icon symbol="caret-down" />
|
|
</button>
|
|
</div>
|
|
<div class="buttons mt-3" v-if="storeLink || isModal">
|
|
<a v-if="storeLink" class="btn btn-secondary rounded-pill w-100" :href="storeLink" :target="isModal ? '_top' : null" v-html="$t('return_to_store', { storeName: srvModel.storeName })" id="StoreLink"></a>
|
|
<button v-else-if="isModal" class="btn btn-secondary rounded-pill w-100" v-on:click="close" v-t="'Close'"></button>
|
|
</div>
|
|
</div>
|
|
<div v-if="isSettled" id="settled" key="settled">
|
|
<div class="top">
|
|
<span class="icn">
|
|
<div id="confetti" v-if="srvModel.celebratePayment" v-on:click="celebratePayment(5000)"></div>
|
|
<vc:icon symbol="checkout-complete" />
|
|
</span>
|
|
<h4 v-t="'invoice_paid'"></h4>
|
|
<div id="PaymentDetails" class="payment-details">
|
|
<dl class="mb-0">
|
|
<div>
|
|
<dt v-t="'invoice_id'"></dt>
|
|
<dd v-text="srvModel.invoiceId" :data-clipboard="srvModel.invoiceId" data-clipboard-hover="start"></dd>
|
|
</div>
|
|
<div v-if="srvModel.orderId">
|
|
<dt v-t="'order_id'"></dt>
|
|
<dd v-text="srvModel.orderId" :data-clipboard="srvModel.orderId" data-clipboard-hover="start"></dd>
|
|
</div>
|
|
</dl>
|
|
<payment-details
|
|
:srv-model="srvModel"
|
|
:is-active="isActive"
|
|
:order-amount="orderAmount"
|
|
:paid="paid"
|
|
:due="due"
|
|
:show-recommended-fee="showRecommendedFee"
|
|
class="mb-5" />
|
|
</div>
|
|
</div>
|
|
<div class="buttons" v-if="srvModel.receiptLink || storeLink || isModal">
|
|
<a v-if="srvModel.receiptLink" class="btn btn-primary rounded-pill w-100" :href="srvModel.receiptLink" :target="isModal ? '_top' : null" v-t="'view_receipt'" id="ReceiptLink"></a>
|
|
<a v-if="storeLink" class="btn btn-secondary rounded-pill w-100" :href="storeLink" :target="isModal ? '_top' : null" v-html="$t('return_to_store', { storeName: srvModel.storeName })" id="StoreLink"></a>
|
|
<button v-else-if="isModal" class="btn btn-secondary rounded-pill w-100" v-on:click="close" v-t="'Close'"></button>
|
|
</div>
|
|
</div>
|
|
<div v-if="isInvalid" id="unpaid" key="unpaid">
|
|
<div class="top">
|
|
<span class="icn">
|
|
<vc:icon symbol="checkout-expired" />
|
|
</span>
|
|
<h4 v-t="'invoice_expired'"></h4>
|
|
<div id="PaymentDetails" class="payment-details">
|
|
<dl class="mb-0">
|
|
<div>
|
|
<dt v-t="'invoice_id'"></dt>
|
|
<dd v-text="srvModel.invoiceId" :data-clipboard="srvModel.invoiceId" data-clipboard-hover="start"></dd>
|
|
</div>
|
|
<div v-if="srvModel.orderId">
|
|
<dt v-t="'order_id'"></dt>
|
|
<dd v-text="srvModel.orderId" :data-clipboard="srvModel.orderId" data-clipboard-hover="start"></dd>
|
|
</div>
|
|
</dl>
|
|
<payment-details
|
|
:srv-model="srvModel"
|
|
:is-active="isActive"
|
|
:order-amount="orderAmount"
|
|
:paid="paid"
|
|
:due="due"
|
|
:show-recommended-fee="showRecommendedFee"
|
|
v-collapsible="displayPaymentDetails" />
|
|
</div>
|
|
<button class="d-flex align-items-center gap-1 btn btn-link payment-details-button" type="button" :aria-expanded="displayPaymentDetails ? 'true' : 'false'" v-on:click="displayPaymentDetails = !displayPaymentDetails">
|
|
<span class="fw-semibold" v-t="'view_details'"></span>
|
|
<vc:icon symbol="caret-down" />
|
|
</button>
|
|
<p class="text-center mt-3" v-html="replaceNewlines($t(isPaidPartial ? 'invoice_paidpartial_body' : 'invoice_expired_body', { storeName: srvModel.storeName, minutes: srvModel.maxTimeMinutes }))"></p>
|
|
</div>
|
|
<div class="buttons" v-if="(isPaidPartial && srvModel.storeSupportUrl) || storeLink || isModal">
|
|
<a v-if="isPaidPartial && srvModel.storeSupportUrl" class="btn btn-primary rounded-pill w-100" :href="srvModel.storeSupportUrl" v-t="'contact_us'" id="ContactLink"></a>
|
|
<a v-if="storeLink" class="btn btn-primary rounded-pill w-100" :href="storeLink" :target="isModal ? '_top' : null" v-html="$t('return_to_store', { storeName: srvModel.storeName })" id="StoreLink"></a>
|
|
<button v-else-if="isModal" class="btn btn-primary rounded-pill w-100" v-on:click="close" v-t="'Close'"></button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
@if (Env.CheatMode)
|
|
{
|
|
<checkout-cheating invoice-id="@Model.InvoiceId" :due="due" :is-settled="isSettled" :is-processing="isProcessing" :payment-method-id="pmId" :crypto-code="srvModel.paymentMethodCurrency"></checkout-cheating>
|
|
}
|
|
<footer class="store-footer">
|
|
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
|
|
{{$t("powered_by")}} <partial name="_StoreFooterLogo" />
|
|
</a>
|
|
<select asp-for="DefaultLang" asp-items="@LangService.GetLanguageSelectListItems()" class="form-select" v-on:change="changeLanguage"></select>
|
|
</footer>
|
|
</div>
|
|
<noscript>
|
|
<div class="p-5 text-center">
|
|
<h2>Javascript is currently disabled in your browser.</h2>
|
|
<h5>Please enable Javascript and refresh this page for the best experience.</h5>
|
|
<p>
|
|
Alternatively, click below to continue to our
|
|
<a asp-action="CheckoutNoScript" asp-route-invoiceId="@Model.InvoiceId">HTML-only invoice</a>.
|
|
</p>
|
|
</div>
|
|
</noscript>
|
|
<script type="text/x-template" id="payment-details">
|
|
<dl>
|
|
<div v-if="orderAmount > 0" id="PaymentDetails-TotalPrice" key="TotalPrice">
|
|
<dt v-t="'total_price'"></dt>
|
|
<dd :data-clipboard="asNumber(srvModel.orderAmount)" data-clipboard-hover="start">{{srvModel.orderAmount}} {{ srvModel.paymentMethodCurrency }}</dd>
|
|
</div>
|
|
<div v-if="orderAmount > 0 && srvModel.orderAmountFiat" id="PaymentDetails-TotalFiat" key="TotalFiat">
|
|
<dt v-t="'total_fiat'"></dt>
|
|
<dd :data-clipboard="asNumber(srvModel.orderAmountFiat)" data-clipboard-hover="start">{{srvModel.orderAmountFiat}}</dd>
|
|
</div>
|
|
<div v-if="srvModel.rate && srvModel.paymentMethodCurrency" id="PaymentDetails-ExchangeRate" key="ExchangeRate">
|
|
<dt v-t="'exchange_rate'"></dt>
|
|
<dd :data-clipboard="asNumber(srvModel.rate)" data-clipboard-hover="start">
|
|
<template v-if="srvModel.paymentMethodCurrency === 'sats'">1 sat = {{ srvModel.rate }}</template>
|
|
<template v-else>1 {{ srvModel.paymentMethodCurrency }} = {{ srvModel.rate }}</template>
|
|
</dd>
|
|
</div>
|
|
<div v-if="srvModel.networkFee" id="PaymentDetails-NetworkCost" key="NetworkCost">
|
|
<dt v-t="'network_cost'"></dt>
|
|
<dd :data-clipboard="asNumber(srvModel.networkFee)" data-clipboard-hover="start">
|
|
<div v-if="srvModel.txCountForFee > 0" v-t="{ path: 'tx_count', args: { count: srvModel.txCount } }"></div>
|
|
<div v-text="`${srvModel.networkFee} ${srvModel.paymentMethodCurrency}`"></div>
|
|
</dd>
|
|
</div>
|
|
<div v-if="paid > 0" id="PaymentDetails-AmountPaid" key="AmountPaid">
|
|
<dt v-t="'amount_paid'"></dt>
|
|
<dd :data-clipboard="asNumber(srvModel.paid)" data-clipboard-hover="start" v-text="`${srvModel.paid} ${srvModel.paymentMethodCurrency}`"></dd>
|
|
</div>
|
|
<div v-if="due > 0" id="PaymentDetails-AmountDue" key="AmountDue">
|
|
<dt v-t="'amount_due'"></dt>
|
|
<dd :data-clipboard="asNumber(srvModel.due)" data-clipboard-hover="start" v-text="`${srvModel.due} ${srvModel.paymentMethodCurrency}`"></dd>
|
|
</div>
|
|
<div v-if="showRecommendedFee" id="PaymentDetails-RecommendedFee" key="RecommendedFee">
|
|
<dt v-t="'recommended_fee'"></dt>
|
|
<dd :data-clipboard="asNumber(srvModel.feeRate)" data-clipboard-hover="start" v-t="{ path: 'fee_rate', args: { feeRate: srvModel.feeRate } }"></dd>
|
|
</div>
|
|
</dl>
|
|
</script>
|
|
<script>
|
|
const checkoutBaseUrl = @Safe.Json(Url.Action("Checkout", new { invoiceId = Model.InvoiceId, paymentMethodId = string.Empty }));
|
|
const i18nUrl = @Safe.Json($"{Model.RootPath}misc/translations/checkout/{{{{lng}}}}?v={Env.Version}");
|
|
const statusUrl = @Safe.Json(Url.Action("GetStatus", new { invoiceId = Model.InvoiceId }));
|
|
const statusWsUrl = @Safe.Json(Url.Action("GetStatusWebSocket", new { invoiceId = Model.InvoiceId }));
|
|
const availableLanguages = @Safe.Json(LangService.GetLanguages().Select(language => language.Code));
|
|
const initialSrvModel = @Safe.Json(Model);
|
|
const qrOptions = { margin: 0, type: 'svg', color: { dark: '#000', light: '#fff' } };
|
|
window.exports = {};
|
|
</script>
|
|
@if (Model.CelebratePayment)
|
|
{
|
|
<script src="~/vendor/dom-confetti/dom-confetti.min.js" asp-append-version="true"></script>
|
|
}
|
|
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
|
<script src="~/vendor/vue-qrcode/vue-qrcode.min.js" asp-append-version="true"></script>
|
|
<script src="~/vendor/i18next/i18next.min.js" asp-append-version="true"></script>
|
|
<script src="~/vendor/i18next/i18nextHttpBackend.min.js" asp-append-version="true"></script>
|
|
<script src="~/vendor/i18next/vue-i18next.js" asp-append-version="true"></script>
|
|
<script src="~/js/copy-to-clipboard.js" asp-append-version="true"></script>
|
|
<script src="~/js/vue-utils.js" asp-append-version="true"></script>
|
|
<script src="~/main/utils.js" asp-append-version="true"></script>
|
|
<script src="~/checkout/checkout.js" asp-append-version="true"></script>
|
|
@if (Env.CheatMode)
|
|
{
|
|
<partial name="Checkout-Cheating" model="@Model" />
|
|
}
|
|
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-payment", model = Model })
|
|
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-end", model = Model })
|
|
</body>
|
|
</html>
|