mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Modal invoice through btcpay.js (#381)
* Modal through btcpay.js * Handling close action depending on whether is modal or not * Tweaking button position * Stripping trailing slashes if present when setting site root
This commit is contained in:
committed by
Nicolas Dorier
parent
c9c7316b7d
commit
aa1ac3da50
@@ -174,7 +174,8 @@ namespace BTCPayServer.Controllers
|
|||||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
||||||
[XFrameOptionsAttribute(null)]
|
[XFrameOptionsAttribute(null)]
|
||||||
[ReferrerPolicyAttribute("origin")]
|
[ReferrerPolicyAttribute("origin")]
|
||||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null, string paymentMethodId = null)
|
public async Task<IActionResult> Checkout(string invoiceId, string id = null, string paymentMethodId = null,
|
||||||
|
[FromQuery]string view = null)
|
||||||
{
|
{
|
||||||
//Keep compatibility with Bitpay
|
//Keep compatibility with Bitpay
|
||||||
invoiceId = invoiceId ?? id;
|
invoiceId = invoiceId ?? id;
|
||||||
@@ -185,6 +186,8 @@ namespace BTCPayServer.Controllers
|
|||||||
if (model == null)
|
if (model == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
if (view == "modal")
|
||||||
|
model.IsModal = true;
|
||||||
|
|
||||||
_CSP.Add(new ConsentSecurityPolicy("script-src", "'unsafe-eval'")); // Needed by Vue
|
_CSP.Add(new ConsentSecurityPolicy("script-src", "'unsafe-eval'")); // Needed by Vue
|
||||||
if (!string.IsNullOrEmpty(model.CustomCSSLink) &&
|
if (!string.IsNullOrEmpty(model.CustomCSSLink) &&
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
public string CustomLogoLink { get; set; }
|
public string CustomLogoLink { get; set; }
|
||||||
public string DefaultLang { get; set; }
|
public string DefaultLang { get; set; }
|
||||||
public List<AvailableCrypto> AvailableCryptos { get; set; } = new List<AvailableCrypto>();
|
public List<AvailableCrypto> AvailableCryptos { get; set; } = new List<AvailableCrypto>();
|
||||||
|
public bool IsModal { get; set; }
|
||||||
public bool IsLightning { get; set; }
|
public bool IsLightning { get; set; }
|
||||||
public string CryptoCode { get; set; }
|
public string CryptoCode { get; set; }
|
||||||
public string ServerUrl { get; set; }
|
public string ServerUrl { get; set; }
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
<img class="header__icon__img" src="~/img/logo-white.png" height="40">
|
<img class="header__icon__img" src="~/img/logo-white.png" height="40">
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="close-icon close-action">
|
||||||
|
✖
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="timer-row">
|
<div class="timer-row">
|
||||||
<div class="timer-row__progress-bar" style="width: 0%;"></div>
|
<div class="timer-row__progress-bar" style="width: 0%;"></div>
|
||||||
@@ -326,9 +329,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="success-message">{{$t("This invoice has been paid")}}</div>
|
<div class="success-message">{{$t("This invoice has been paid")}}</div>
|
||||||
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink">
|
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink && !isModal">
|
||||||
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
<button class="action-button close-action" v-show="isModal">
|
||||||
|
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-wrapper refund-address-form-container" id="refund-overpayment-button">
|
<div class="button-wrapper refund-address-form-container" id="refund-overpayment-button">
|
||||||
@@ -351,7 +357,7 @@
|
|||||||
|
|
||||||
<div class="bp-view expired" id="expired">
|
<div class="bp-view expired" id="expired">
|
||||||
<div>
|
<div>
|
||||||
<div class="expired__body">
|
<div class="expired__body" style="margin-bottom: 20px;">
|
||||||
<div class="expired__header">{{$t("What happened?")}}</div>
|
<div class="expired__header">{{$t("What happened?")}}</div>
|
||||||
<div class="expired__text" i18n="">
|
<div class="expired__text" i18n="">
|
||||||
{{$t("InvoiceExpired_Body_1", {storeName: srvModel.storeName, maxTimeMinutes: @Model.MaxTimeMinutes})}}
|
{{$t("InvoiceExpired_Body_1", {storeName: srvModel.storeName, maxTimeMinutes: @Model.MaxTimeMinutes})}}
|
||||||
@@ -370,10 +376,12 @@
|
|||||||
{{srvModel.orderId}}
|
{{srvModel.orderId}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink"
|
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink && !isModal">
|
||||||
style="margin-top: 20px;">
|
|
||||||
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
<button class="action-button close-action" v-show="isModal">
|
||||||
|
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,10 +27,20 @@
|
|||||||
<link href="@Model.CustomCSSLink" rel="stylesheet" />
|
<link href="@Model.CustomCSSLink" rel="stylesheet" />
|
||||||
}
|
}
|
||||||
|
|
||||||
<style type="text/css">
|
@if (Model.IsModal)
|
||||||
</style>
|
{
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
background: rgba(55, 58, 60, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
</head>
|
</head>
|
||||||
<body style="background: #E4E4E4">
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<center style="padding: 2em">
|
<center style="padding: 2em">
|
||||||
<h2>Javascript is currently disabled in your browser.</h2>
|
<h2>Javascript is currently disabled in your browser.</h2>
|
||||||
@@ -53,29 +63,29 @@
|
|||||||
</center>
|
</center>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
|
|
||||||
<invoice>
|
<invoice>
|
||||||
<div class="no-bounce" id="checkoutCtrl" v-cloak>
|
<div class="no-bounce" id="checkoutCtrl" v-cloak>
|
||||||
<div class="modal page">
|
<div class="modal page">
|
||||||
<div class="modal-dialog open opened enter-purchaser-email" role="document">
|
<div class="modal-dialog open opened enter-purchaser-email" role="document">
|
||||||
<div class="modal-content long">
|
<div class="modal-content long">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="invoice">
|
<div class="invoice">
|
||||||
<partial name="Checkout-Body" />
|
<partial name="Checkout-Body" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 10px; text-align: center;">
|
<div style="margin-top: 10px; text-align: center;">
|
||||||
@* Not working because of nsSeparator: false, keySeparator: false,
|
@* Not working because of nsSeparator: false, keySeparator: false,
|
||||||
{{$t("nested.lang")}} >>
|
{{$t("nested.lang")}} >>
|
||||||
*@
|
*@
|
||||||
<select class="cmblang reverse invisible" onchange="changeLanguage($(this).val())">
|
<select class="cmblang reverse invisible" onchange="changeLanguage($(this).val())">
|
||||||
@foreach (var lang in langService.GetLanguages())
|
@foreach (var lang in langService.GetLanguages())
|
||||||
{
|
{
|
||||||
<option value="@lang.Code">@lang.DisplayName</option>
|
<option value="@lang.Code">@lang.DisplayName</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
<script>
|
<script>
|
||||||
$(function() {
|
$(function() {
|
||||||
var storeDefaultLang = '@Model.DefaultLang';
|
var storeDefaultLang = '@Model.DefaultLang';
|
||||||
if (urlParams.lang) {
|
if (urlParams.lang) {
|
||||||
@@ -96,10 +106,11 @@
|
|||||||
hoverIntent: 5000
|
hoverIntent: 5000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 10px; text-align: center;" class="form-text small text-muted">
|
<div style="margin-top: 10px; text-align: center;" class="form-text small text-muted">
|
||||||
<span>Powered by <a target="_blank" href="https://github.com/btcpayserver/btcpayserver">BTCPay Server</a></span>
|
<span>Powered by <a target="_blank" href="https://github.com/btcpayserver/btcpayserver">BTCPay Server</a></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,7 +164,7 @@
|
|||||||
// Ignoring custom HTML5 elements, eg: bp-spinner
|
// Ignoring custom HTML5 elements, eg: bp-spinner
|
||||||
/^bp-/
|
/^bp-/
|
||||||
];
|
];
|
||||||
|
|
||||||
var checkoutCtrl = new Vue({
|
var checkoutCtrl = new Vue({
|
||||||
i18n: i18n,
|
i18n: i18n,
|
||||||
el: '#checkoutCtrl',
|
el: '#checkoutCtrl',
|
||||||
@@ -162,12 +173,13 @@
|
|||||||
changelly: ChangellyComponent
|
changelly: ChangellyComponent
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
srvModel: srvModel,
|
srvModel: srvModel,
|
||||||
lndModel: null,
|
lndModel: null,
|
||||||
scanDisplayQr: "",
|
scanDisplayQr: "",
|
||||||
expiringSoon: false
|
expiringSoon: false,
|
||||||
|
isModal: '@(Model.IsModal ? "true" : "false")'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ html {
|
|||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
article,
|
article,
|
||||||
aside,
|
aside,
|
||||||
details,
|
details,
|
||||||
@@ -333,8 +329,9 @@ body {
|
|||||||
font-family: "Roboto", "Helvetica", sans-serif;
|
font-family: "Roboto", "Helvetica", sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #373a3c;
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
margin: 0;
|
||||||
|
background: #E4E4E4;
|
||||||
}
|
}
|
||||||
|
|
||||||
[tabindex="-1"]:focus {
|
[tabindex="-1"]:focus {
|
||||||
@@ -1296,10 +1293,6 @@ html {
|
|||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
article,
|
article,
|
||||||
aside,
|
aside,
|
||||||
details,
|
details,
|
||||||
@@ -1619,14 +1612,6 @@ html {
|
|||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: "Roboto", "Helvetica", sans-serif;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #373a3c;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
[tabindex="-1"]:focus {
|
[tabindex="-1"]:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
@@ -9084,11 +9069,12 @@ strong {
|
|||||||
|
|
||||||
.close-icon {
|
.close-icon {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: none;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 13px;
|
padding: 13px;
|
||||||
transition: opacity 250ms ease;
|
transition: opacity 250ms ease;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-icon img {
|
.close-icon img {
|
||||||
@@ -10373,7 +10359,7 @@ All mobile class names should be prefixed by m- */
|
|||||||
}
|
}
|
||||||
|
|
||||||
.paid:not(.paid-over) .status-icon {
|
.paid:not(.paid-over) .status-icon {
|
||||||
padding-top: 54px;
|
padding-top: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paid:not(.paid-over) .button-wrapper {
|
.paid:not(.paid-over) .button-wrapper {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ function changeCurrency(currency) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onDataCallback(jsonData) {
|
function onDataCallback(jsonData) {
|
||||||
|
|
||||||
var newStatus = jsonData.status;
|
var newStatus = jsonData.status;
|
||||||
|
|
||||||
if (newStatus === "complete" ||
|
if (newStatus === "complete" ||
|
||||||
@@ -123,20 +122,6 @@ $(document).ready(function () {
|
|||||||
// initialize
|
// initialize
|
||||||
onDataCallback(srvModel);
|
onDataCallback(srvModel);
|
||||||
|
|
||||||
/* TAF
|
|
||||||
|
|
||||||
- Version mobile
|
|
||||||
|
|
||||||
- Réparer le décallage par timer
|
|
||||||
|
|
||||||
- Preparer les variables de l'API
|
|
||||||
|
|
||||||
- Gestion des differents evenements en fonction du status de l'invoice
|
|
||||||
|
|
||||||
- sécuriser les CDN
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// check if the Document expired
|
// check if the Document expired
|
||||||
if (srvModel.expirationSeconds > 0) {
|
if (srvModel.expirationSeconds > 0) {
|
||||||
progressStart(srvModel.maxTimeSeconds); // Progress bar
|
progressStart(srvModel.maxTimeSeconds); // Progress bar
|
||||||
@@ -147,7 +132,19 @@ $(document).ready(function () {
|
|||||||
hideEmailForm();
|
hideEmailForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(".close-action").on("click", function () {
|
||||||
|
$("invoice").fadeOut(300, function () {
|
||||||
|
window.parent.postMessage("close", "*");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.parent.postMessage("loaded", "*");
|
||||||
|
jQuery("invoice").fadeOut(0);
|
||||||
|
jQuery("invoice").fadeIn(300);
|
||||||
|
|
||||||
|
// eof initialize
|
||||||
|
|
||||||
|
// FUNCTIONS
|
||||||
function hideEmailForm() {
|
function hideEmailForm() {
|
||||||
$("#emailAddressView").removeClass("active");
|
$("#emailAddressView").removeClass("active");
|
||||||
$("placeholder-refundEmail").html(srvModel.customerEmail);
|
$("placeholder-refundEmail").html(srvModel.customerEmail);
|
||||||
|
|||||||
117
BTCPayServer/wwwroot/modal/btcpay.js
Normal file
117
BTCPayServer/wwwroot/modal/btcpay.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/* jshint browser: true, strict: false, maxlen: false, maxstatements: false */
|
||||||
|
(function () {
|
||||||
|
function warn() {
|
||||||
|
if (window.console && window.console.warn) {
|
||||||
|
window.console.warn.apply(window.console, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.btcpay) {
|
||||||
|
warn('btcpay.js attempted to initialize more than once.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var iframe = document.createElement('iframe');
|
||||||
|
iframe.name = 'btcpay';
|
||||||
|
iframe.class = 'btcpay';
|
||||||
|
iframe.setAttribute('allowtransparency', 'true');
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
iframe.style.border = 0;
|
||||||
|
iframe.style.position = 'fixed';
|
||||||
|
iframe.style.top = 0;
|
||||||
|
iframe.style.left = 0;
|
||||||
|
iframe.style.height = '100%';
|
||||||
|
iframe.style.width = '100%';
|
||||||
|
iframe.style.zIndex = '1000';
|
||||||
|
|
||||||
|
var origin = 'http://slack.btcpayserver.org join us there, and initialize this with your origin url through setApiUrlPrefix';
|
||||||
|
// urlPrefix should be site root without trailing slash
|
||||||
|
function setApiUrlPrefix(urlPrefix) {
|
||||||
|
origin = stripTrailingSlashes(urlPrefix);
|
||||||
|
}
|
||||||
|
function stripTrailingSlashes(site) {
|
||||||
|
return site.replace(/\/+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
var onModalWillEnterMethod = function () { };
|
||||||
|
var onModalWillLeaveMethod = function () { };
|
||||||
|
|
||||||
|
function showFrame() {
|
||||||
|
if (window.document.getElementsByName('btcpay').length === 0) {
|
||||||
|
window.document.body.appendChild(iframe);
|
||||||
|
}
|
||||||
|
onModalWillEnterMethod();
|
||||||
|
iframe.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideFrame() {
|
||||||
|
onModalWillLeaveMethod();
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
iframe = window.document.body.removeChild(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onModalWillEnter(customOnModalWillEnter) {
|
||||||
|
onModalWillEnterMethod = customOnModalWillEnter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onModalWillLeave(customOnModalWillLeave) {
|
||||||
|
onModalWillLeaveMethod = customOnModalWillLeave;
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveMessage(event) {
|
||||||
|
var uri;
|
||||||
|
|
||||||
|
if (origin !== event.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.data === 'close') {
|
||||||
|
hideFrame();
|
||||||
|
} else if (event.data === 'loaded') {
|
||||||
|
showFrame();
|
||||||
|
} else if (event.data && event.data.open) {
|
||||||
|
uri = event.data.open;
|
||||||
|
if (uri.indexOf('bitcoin:') === 0) {
|
||||||
|
window.location = uri;
|
||||||
|
}
|
||||||
|
} else if (event.data && event.data.mailto) {
|
||||||
|
uri = event.data.mailto;
|
||||||
|
if (uri.indexOf('mailto:') === 0) {
|
||||||
|
window.location = uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showInvoice(invoiceId, params) {
|
||||||
|
window.document.body.appendChild(iframe);
|
||||||
|
var invoiceUrl = origin + '/invoice?id=' + invoiceId + '&view=modal';
|
||||||
|
if (params && params.animateEntrance === false) {
|
||||||
|
invoiceUrl += '&animateEntrance=false';
|
||||||
|
}
|
||||||
|
iframe.src = invoiceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButtonListeners() {
|
||||||
|
var buttons = window.document.querySelectorAll('[data-btcpay-button]');
|
||||||
|
for (var i = 0; i < buttons.length; i++) {
|
||||||
|
var b = buttons[0];
|
||||||
|
b.addEventListener('submit', showFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', function load() {
|
||||||
|
window.removeEventListener('load', load);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('message', receiveMessage, false);
|
||||||
|
setButtonListeners();
|
||||||
|
|
||||||
|
window.btcpay = {
|
||||||
|
showFrame: showFrame,
|
||||||
|
hideFrame: hideFrame,
|
||||||
|
showInvoice: showInvoice,
|
||||||
|
onModalWillEnter: onModalWillEnter,
|
||||||
|
onModalWillLeave: onModalWillLeave,
|
||||||
|
setApiUrlPrefix: setApiUrlPrefix
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
11
BTCPayServer/wwwroot/modal/test.html
Normal file
11
BTCPayServer/wwwroot/modal/test.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="btcpay.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
btcpay.setApiUrlPrefix("http://127.0.0.1:14142");
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<button onclick="btcpay.showInvoice('25DKDgcB4z4PtkPJyHVYnV')">Open Modal</button>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user