btcpayserver/BTCPayServer/Views/Invoice/Checkout.cshtml

399 lines
16 KiB
Plaintext
Raw Normal View History

@addTagHelper *, BundlerMinifier.TagHelpers
2018-03-23 09:27:48 +01:00
@inject BTCPayServer.Services.LanguageService langService
@inject BTCPayNetworkProvider BTCPayNetworkProvider
2021-10-11 05:32:09 +02:00
@inject BTCPayServer.Services.BTCPayServerEnvironment env
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@using NBitcoin
@model PaymentModel
2017-09-13 08:47:34 +02:00
@{
Layout = null;
2017-09-13 08:47:34 +02:00
}
2017-09-13 08:47:34 +02:00
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<META NAME="robots" CONTENT="noindex,nofollow">
2018-05-03 23:51:04 +02:00
<title>@Model.HtmlTitle</title>
2017-09-13 08:47:34 +02:00
<bundle name="wwwroot/bundles/checkout-bundle.min.css" asp-append-version="true" />
Bootstrap v5 migration (#2490) * Swap bootstrap asset files * Update themes and color definitions * Move general bootstrap customizations * Theme updates Theme updates * Remove BuildBundlerMinifier This lead to an error, because BuildBundlerMinifier and BundlerMinifier.Core seem to conflict here. Details: https://stackoverflow.com/a/61119586 * Rewplace btn-block class with w-100 * Update badge classes * Remove old font family head variable * Update margin classes * Cleanups * Update float classes * Update text classes * Update padding classes * Update border classes * UPdate dropdown classes * Update select classes * Update neutral custom props * Update bootstrap and customizations * Update ChromeDriver; disable smooth scroll https://github.com/SeleniumHQ/selenium/issues/8295 * Improve alert messages * Improve bootstrap customizations * Disable reduced motion See also 7358282f * Update Bootstrap data attributes * Update file inputs * Update input groups * Replace deprecated jumbotron class * Update variables; re-add negative margin util classes * Update cards * Update form labels * Debug alerts * Fix aria-labelledby associations * Dropdown-related test fixes * Fix CanUseWebhooks test * Test fixes * Nav updates * Fix nav usage in wallet send and payouts * Update alert and modal close buttons * Re-add backdrop properties * Upgrade Bootstrap to v5 final * Update screen reader classes * Update font-weight classes * Update monospace font classes * Update accordians * Update close icon usage * Cleanup * Update scripts and style integrations * Update input group texts * Update LN node setup page * Update more form control classes * Update inline forms * Add js specific test * Upgrade Vue.js * Remove unused JS * Upgrade Bootstrap to v5.0.1 * Try container related test updates * Separate jQuery bundle * Remove jQuery from LND seed backup page * Remove unused code * Refactor email autofill js * Refactor camera scanner JS * Re-add tests * Re-add BuildBundlerMinifier * Do not minify bundles containing Bootstrap Details https://github.com/madskristensen/BundlerMinifier/issues/558 * Update bundles * Cleanup JS test * Cleanup tests involving dropdowns * Cleanup tests involving collapses * Cleanup locale additions in ConfigureCore * Cleanup bundles * Remove duplicate status message * Cleanup formatting * Fix missing validation scripts * Remove unused unminified Bootstrap js files * Fix classic theme * Fix Casa theme * Fix PoS validation
2021-05-19 04:39:27 +02:00
<script>
var initialSrvModel = @Safe.Json(Model);
</script>
Bootstrap v5 migration (#2490) * Swap bootstrap asset files * Update themes and color definitions * Move general bootstrap customizations * Theme updates Theme updates * Remove BuildBundlerMinifier This lead to an error, because BuildBundlerMinifier and BundlerMinifier.Core seem to conflict here. Details: https://stackoverflow.com/a/61119586 * Rewplace btn-block class with w-100 * Update badge classes * Remove old font family head variable * Update margin classes * Cleanups * Update float classes * Update text classes * Update padding classes * Update border classes * UPdate dropdown classes * Update select classes * Update neutral custom props * Update bootstrap and customizations * Update ChromeDriver; disable smooth scroll https://github.com/SeleniumHQ/selenium/issues/8295 * Improve alert messages * Improve bootstrap customizations * Disable reduced motion See also 7358282f * Update Bootstrap data attributes * Update file inputs * Update input groups * Replace deprecated jumbotron class * Update variables; re-add negative margin util classes * Update cards * Update form labels * Debug alerts * Fix aria-labelledby associations * Dropdown-related test fixes * Fix CanUseWebhooks test * Test fixes * Nav updates * Fix nav usage in wallet send and payouts * Update alert and modal close buttons * Re-add backdrop properties * Upgrade Bootstrap to v5 final * Update screen reader classes * Update font-weight classes * Update monospace font classes * Update accordians * Update close icon usage * Cleanup * Update scripts and style integrations * Update input group texts * Update LN node setup page * Update more form control classes * Update inline forms * Add js specific test * Upgrade Vue.js * Remove unused JS * Upgrade Bootstrap to v5.0.1 * Try container related test updates * Separate jQuery bundle * Remove jQuery from LND seed backup page * Remove unused code * Refactor email autofill js * Refactor camera scanner JS * Re-add tests * Re-add BuildBundlerMinifier * Do not minify bundles containing Bootstrap Details https://github.com/madskristensen/BundlerMinifier/issues/558 * Update bundles * Cleanup JS test * Cleanup tests involving dropdowns * Cleanup tests involving collapses * Cleanup locale additions in ConfigureCore * Cleanup bundles * Remove duplicate status message * Cleanup formatting * Fix missing validation scripts * Remove unused unminified Bootstrap js files * Fix classic theme * Fix Casa theme * Fix PoS validation
2021-05-19 04:39:27 +02:00
<bundle name="wwwroot/bundles/bootstrap-bundle.min.js" asp-append-version="true" />
<bundle name="wwwroot/bundles/checkout-bundle.min.js" asp-append-version="true" />
<script>vex.defaultOptions.className = 'vex-theme-btcpay'</script>
@if (!string.IsNullOrEmpty(Model.CustomCSSLink))
{
<link href="@Model.CustomCSSLink" rel="stylesheet" />
}
@if (Model.IsModal)
{
<style type="text/css">
body {
background: rgba(25, 25, 25, 0.9);
}
2021-10-11 05:32:09 +02:00
.close-icon {
display: flex;
}
</style>
}
2017-09-13 08:47:34 +02:00
</head>
<body>
<noscript>
<div style="padding:2em;text-align:center;">
<h2>Javascript is currently disabled in your browser.</h2>
<h5>Please enable Javascript and refresh this page for the best experience.</h5>
2017-09-13 08:47:34 +02:00
<p>Alternatively, click below to continue to our HTML-only invoice.</p>
2017-09-13 08:47:34 +02:00
<a asp-action="CheckoutNoScript" asp-route-invoiceId="@Model.InvoiceId" style="text-decoration:underline;color:blue">
2019-03-31 20:46:38 +02:00
Continue to javascript-disabled invoice &gt;
</a>
</div>
</noscript>
2017-09-13 08:47:34 +02:00
<!--[if lte IE 8]>
<div style="padding:2em;text-align:center;">
<form asp-action="CheckoutNoScript" method="GET">
<button style="text-decoration: underline; color: blue">Continue to legacy browser compatible invoice page
</button>
</form>
</div>
<![endif]-->
2017-09-13 08:47:34 +02:00
<invoice>
<div class="no-bounce" id="checkoutCtrl" v-cloak>
<div class="modal page">
<div class="modal-dialog open opened" role="document" v-bind:class="{ 'expired': invoiceUnpayable, 'paid': invoicePaid, 'enter-purchaser-email': showEmailForm}">
<div class="modal-content long">
<div class="content">
<div class="invoice">
<partial name="Checkout-Body" />
</div>
</div>
</div>
<div style="margin-top: 10px; text-align: center;">
@* Not working because of nsSeparator: false, keySeparator: false,
{{$t("nested.lang")}} >>
*@
<select asp-for="DefaultLang"
class="cmblang reverse invisible"
asp-items="@langService.GetLanguageSelectListItems()"></select>
<script>
$(function () {
// REVIEW: don't use initDropdown method but rather directly initialize select whenever you are using it
$("#DefaultLang").val(startingLanguage);
var languageSelectorPrettyDropdown = initDropdown("#DefaultLang");
2021-10-11 05:32:09 +02:00
languageSelectorPrettyDropdown.change(function () {
changeLanguage(languageSelectorPrettyDropdown.val());
});
2021-10-11 05:32:09 +02:00
languageSelectorPrettyDropdown.keypress(function (event) {
if (event.keyCode == 13) {
languageSelectorPrettyDropdown.click();
}
});
});
function initDropdown(selector) {
return $(selector).prettyDropdown({
classic: true,
height: 32,
reverse: true,
hoverIntent: -1
});
}
</script>
</div>
<div class="powered__by__btcpayserver">
Powered by <a target="_blank" href="https://github.com/btcpayserver/btcpayserver" rel="noreferrer noopener">BTCPay Server</a>
</div>
2021-10-11 05:32:09 +02:00
@if (env.CheatMode)
{
<partial name="Checkout-Testing" />
}
</div>
</div>
</div>
</invoice>
Bootstrap v5 migration (#2490) * Swap bootstrap asset files * Update themes and color definitions * Move general bootstrap customizations * Theme updates Theme updates * Remove BuildBundlerMinifier This lead to an error, because BuildBundlerMinifier and BundlerMinifier.Core seem to conflict here. Details: https://stackoverflow.com/a/61119586 * Rewplace btn-block class with w-100 * Update badge classes * Remove old font family head variable * Update margin classes * Cleanups * Update float classes * Update text classes * Update padding classes * Update border classes * UPdate dropdown classes * Update select classes * Update neutral custom props * Update bootstrap and customizations * Update ChromeDriver; disable smooth scroll https://github.com/SeleniumHQ/selenium/issues/8295 * Improve alert messages * Improve bootstrap customizations * Disable reduced motion See also 7358282f * Update Bootstrap data attributes * Update file inputs * Update input groups * Replace deprecated jumbotron class * Update variables; re-add negative margin util classes * Update cards * Update form labels * Debug alerts * Fix aria-labelledby associations * Dropdown-related test fixes * Fix CanUseWebhooks test * Test fixes * Nav updates * Fix nav usage in wallet send and payouts * Update alert and modal close buttons * Re-add backdrop properties * Upgrade Bootstrap to v5 final * Update screen reader classes * Update font-weight classes * Update monospace font classes * Update accordians * Update close icon usage * Cleanup * Update scripts and style integrations * Update input group texts * Update LN node setup page * Update more form control classes * Update inline forms * Add js specific test * Upgrade Vue.js * Remove unused JS * Upgrade Bootstrap to v5.0.1 * Try container related test updates * Separate jQuery bundle * Remove jQuery from LND seed backup page * Remove unused code * Refactor email autofill js * Refactor camera scanner JS * Re-add tests * Re-add BuildBundlerMinifier * Do not minify bundles containing Bootstrap Details https://github.com/madskristensen/BundlerMinifier/issues/558 * Update bundles * Cleanup JS test * Cleanup tests involving dropdowns * Cleanup tests involving collapses * Cleanup locale additions in ConfigureCore * Cleanup bundles * Remove duplicate status message * Cleanup formatting * Fix missing validation scripts * Remove unused unminified Bootstrap js files * Fix classic theme * Fix Casa theme * Fix PoS validation
2021-05-19 04:39:27 +02:00
<script>
var availableLanguages = @Safe.Json(langService.GetLanguages().Select((language) => language.Code));
var defaultLang = @Safe.Json(Model.DefaultLang);
var fallbackLanguage = "en";
startingLanguage = computeStartingLanguage();
i18next
.use(window.i18nextXHRBackend)
.init({
backend: {
loadPath: @Safe.Json($"{Model.RootPath}locales/{{{{lng}}}}.json")
},
lng: startingLanguage,
fallbackLng: fallbackLanguage,
nsSeparator: false,
keySeparator: false
});
function computeStartingLanguage() {
if (urlParams.lang && isLanguageAvailable(urlParams.lang)) {
return urlParams.lang;
}
else if (isLanguageAvailable(defaultLang)) {
return defaultLang;
} else {
return fallbackLanguage;
}
}
function changeLanguage(lang) {
if (isLanguageAvailable(lang)) {
i18next.changeLanguage(lang);
}
}
function isLanguageAvailable(languageCode) {
return availableLanguages.indexOf(languageCode) >= 0;
}
var i18n = new VueI18next(i18next);
Vue.config.ignoredElements = [
'line-items',
'low-fee-timeline',
// Ignoring custom HTML5 elements, eg: bp-spinner
/^bp-/
];
var eventBus = new Vue();
var checkoutCtrl = new Vue({
i18n: i18n,
el: '#checkoutCtrl',
data: {
srvModel: initialSrvModel,
end: new Date(),
expirationPercentage: 0,
timerText: "@Model.TimeLeft",
emailAddressInput: "",
emailAddressInputDirty: false,
emailAddressInputInvalid: false,
emailAddressFormSubmitting: false,
lineItemsExpanded: false,
changingCurrencies: false,
loading: true,
isModal: initialSrvModel.isModal
2019-04-06 08:10:27 +02:00
},
computed: {
expiringSoon: function(){
return this.expirationPercentage >= 75 && !this.invoiceUnpayable && !this.invoicePaid;
},
showPaymentUI: function(){
return !this.showEmailForm && !this.invoiceUnpayable && !this.invoicePaid;
},
showEmailForm: function(){
return this.srvModel.requiresRefundEmail && (!this.srvModel.customerEmail || !this.validateEmail(this.srvModel.customerEmail)) && !this.invoiceUnpayable;
},
showRecommendedFee: function(){
return this.srvModel.showRecommendedFee && this.srvModel.feeRate != 0;
},
invoiceUnpayable: function(){
return ["expired", "invalid"].indexOf(this.srvModel.status) >= 0;
},
invoicePaid: function(){
return ["complete", "confirmed", "paid"].indexOf(this.srvModel.status) >= 0;
}
},
mounted: function(){
this.startProgressTimer();
this.listenIn();
this.onDataCallback(this.srvModel);
if (this.srvModel.status === "new" && this.srvModel.txCount > 1) {
this.onlyExpandLineItems();
}
window.parent.postMessage("loaded", "*");
jQuery("invoice").fadeOut(0).fadeIn(300);
window.closePaymentMethodDialog = this.closePaymentMethodDialog.bind(this);
this.loading = false;
},
methods: {
onlyExpandLineItems: function() {
if (!this.lineItemsExpanded) {
this.toggleLineItems();
}},
toggleLineItems: function() {
this.lineItemsExpanded ? $("line-items").slideUp() : $("line-items").slideDown();
this.lineItemsExpanded = !this.lineItemsExpanded;
},
openPaymentMethodDialog: function() {
var content = $("#vexPopupDialog").html();
vex.open({
unsafeContent: content
});
},
closePaymentMethodDialog: function(currencyId) {
vex.closeAll();
this.changeCurrency(currencyId);
2021-10-11 05:32:09 +02:00
},
changeCurrency: function (currency) {
if (currency !== null && this.srvModel.paymentMethodId !== currency) {
this.changingCurrencies = true;
this.srvModel.paymentMethodId = currency;
this.fetchData();
this.closePaymentMethodDialog(null);
}
2021-10-11 05:32:09 +02:00
},
close: function(){
$("invoice").fadeOut(300, function () {
window.parent.postMessage("close", "*");
});
},
validateEmail: function (email) {
var re = /^(([^<>()\[\]\\.,;:\s@@"]+(\.[^<>()\[\]\\.,;:\s@@"]+)*)|(".+"))@@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
},
startProgressTimer: function(){
var timeLeftS = this.endDate? (this.endDate.getTime() - new Date().getTime())/1000 : this.srvModel.expirationSeconds;
this.expirationPercentage = 100 - ((timeLeftS / this.srvModel.maxTimeSeconds) * 100);
this.timerText = this.updateTimerText(timeLeftS);
if( this.expirationPercentage < 100 && (this.srvModel.status === "paidPartial" || this.srvModel.status === "new")){
setTimeout(this.startProgressTimer, 500);
}
},
updateTimerText: function (timer) {
if (timer >= 0) {
var minutes = parseInt(timer / 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
var seconds = parseInt(timer % 60, 10);
seconds = seconds < 10 ? "0" + seconds : seconds;
return minutes + ":" + seconds;
} else {
return "00:00";
}
},
listenIn: function(){
2019-08-29 08:37:02 +02:00
var self = this;
var socket = null;
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
if (supportsWebSockets) {
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += loc.pathname + "/status/ws?invoiceId=" + this.srvModel.invoiceId;
try {
socket = new WebSocket(ws_uri);
socket.onmessage = function (e) {
if (e.data === "ping")
return;
2019-08-29 08:37:02 +02:00
self.fetchData();
};
socket.onerror = function (e) {
console.error("Error while connecting to websocket for invoice notifications (callback)");
};
}
catch (e) {
console.error("Error while connecting to websocket for invoice notifications");
}
}
2019-09-05 05:13:36 +02:00
var self = this;
function watcher(){
setTimeout(function(){
if (socket === null || socket.readyState !== 1) {
2019-09-05 05:13:36 +02:00
self.fetchData();
}
watcher();
}, 2000);
2021-10-11 05:32:09 +02:00
}
watcher();
},
fetchData: function(){
var self = this;
$.ajax({
url: window.location.pathname + "/status?invoiceId=" + this.srvModel.invoiceId + "&paymentMethodId=" + this.srvModel.paymentMethodId,
type: "GET",
cache: false
})
.done(function (data) {
self.onDataCallback.bind(self)(data);
})
},
onDataCallback : function(jsonData){
if (this.srvModel.status !== jsonData.status) {
window.parent.postMessage({ "invoiceId": this.srvModel.invoiceId, "status": jsonData.status }, "*");
}
if (jsonData.paymentMethodId === this.srvModel.paymentMethodId) {
this.changingCurrencies = false;
}
// displaying satoshis for lightning payments
jsonData.cryptoCodeSrv = jsonData.cryptoCode;
// expand line items to show details on amount due for multi-transaction payment
if (this.srvModel.txCount === 1 && jsonData.txCount > 1) {
this.onlyExpandLineItems();
}
var newEnd = new Date();
newEnd.setSeconds(newEnd.getSeconds()+ jsonData.expirationSeconds);
this.endDate = newEnd;
// updating ui
this.srvModel = jsonData;
2021-10-11 05:32:09 +02:00
eventBus.$emit("data-fetched", this.srvModel);
if (this.invoicePaid && jsonData.redirectAutomatically && jsonData.merchantRefLink) {
this.loading = true;
setTimeout(function () {
if (this.isModal && window.top.location == jsonData.merchantRefLink){
this.close();
} else {
window.top.location = jsonData.merchantRefLink;
}
}.bind(this), 2000);
}
},
onEmailChange: function(){
this.emailAddressInputDirty = true;
this.emailAddressInputInvalid = false;
},
onEmailSubmit : function(){
var self = this;
if (this.validateEmail(this.emailAddressInput)) {
this.emailAddressFormSubmitting = true;
// Push the email to a server, once the reception is confirmed move on
$.ajax({
url: window.location.pathname + "/UpdateCustomer?invoiceId=" +this.srvModel.invoiceId,
type: "POST",
data: JSON.stringify({ Email: this.emailAddressInput }),
contentType: "application/json; charset=utf-8"
})
.done(function () {
self.srvModel.customerEmail = self.emailAddressInput;
}).always(function () {
self.emailAddressFormSubmitting = false;
});
} else {
this.emailAddressInputInvalid = true;
}
2019-04-06 08:10:27 +02:00
}
}
});
</script>
2021-10-11 05:32:09 +02:00
@foreach (var paymentMethodHandler in PaymentMethodHandlerDictionary.Select(handler => handler.GetCheckoutUISettings()).Where(settings => settings != null))
{
<partial name="@paymentMethodHandler.ExtensionPartial" model="@Model" />
}
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-end", model = Model })
2017-09-13 08:47:34 +02:00
</body>
</html>