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)]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
[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
|
||||
invoiceId = invoiceId ?? id;
|
||||
@@ -185,6 +186,8 @@ namespace BTCPayServer.Controllers
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
if (view == "modal")
|
||||
model.IsModal = true;
|
||||
|
||||
_CSP.Add(new ConsentSecurityPolicy("script-src", "'unsafe-eval'")); // Needed by Vue
|
||||
if (!string.IsNullOrEmpty(model.CustomCSSLink) &&
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string CustomLogoLink { get; set; }
|
||||
public string DefaultLang { get; set; }
|
||||
public List<AvailableCrypto> AvailableCryptos { get; set; } = new List<AvailableCrypto>();
|
||||
public bool IsModal { get; set; }
|
||||
public bool IsLightning { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string ServerUrl { get; set; }
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<img class="header__icon__img" src="~/img/logo-white.png" height="40">
|
||||
}
|
||||
</div>
|
||||
<div class="close-icon close-action">
|
||||
✖
|
||||
</div>
|
||||
</div>
|
||||
<div class="timer-row">
|
||||
<div class="timer-row__progress-bar" style="width: 0%;"></div>
|
||||
@@ -326,9 +329,12 @@
|
||||
</div>
|
||||
</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>
|
||||
</a>
|
||||
<button class="action-button close-action" v-show="isModal">
|
||||
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-wrapper refund-address-form-container" id="refund-overpayment-button">
|
||||
@@ -351,7 +357,7 @@
|
||||
|
||||
<div class="bp-view expired" id="expired">
|
||||
<div>
|
||||
<div class="expired__body">
|
||||
<div class="expired__body" style="margin-bottom: 20px;">
|
||||
<div class="expired__header">{{$t("What happened?")}}</div>
|
||||
<div class="expired__text" i18n="">
|
||||
{{$t("InvoiceExpired_Body_1", {storeName: srvModel.storeName, maxTimeMinutes: @Model.MaxTimeMinutes})}}
|
||||
@@ -370,10 +376,12 @@
|
||||
{{srvModel.orderId}}
|
||||
</div>
|
||||
</div>
|
||||
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink"
|
||||
style="margin-top: 20px;">
|
||||
<a class="action-button" :href="srvModel.merchantRefLink" v-show="srvModel.merchantRefLink && !isModal">
|
||||
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||
</a>
|
||||
<button class="action-button close-action" v-show="isModal">
|
||||
<span>{{$t("Return to StoreName", srvModel)}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,10 +27,20 @@
|
||||
<link href="@Model.CustomCSSLink" rel="stylesheet" />
|
||||
}
|
||||
|
||||
@if (Model.IsModal)
|
||||
{
|
||||
<style type="text/css">
|
||||
body {
|
||||
background: rgba(55, 58, 60, 0.4);
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
</head>
|
||||
<body style="background: #E4E4E4">
|
||||
<body>
|
||||
<noscript>
|
||||
<center style="padding: 2em">
|
||||
<h2>Javascript is currently disabled in your browser.</h2>
|
||||
@@ -103,6 +113,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</invoice>
|
||||
<script type="text/javascript">
|
||||
var storeDefaultLang = '@Model.DefaultLang';
|
||||
@@ -165,7 +176,8 @@
|
||||
srvModel: srvModel,
|
||||
lndModel: null,
|
||||
scanDisplayQr: "",
|
||||
expiringSoon: false
|
||||
expiringSoon: false,
|
||||
isModal: '@(Model.IsModal ? "true" : "false")'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -6,10 +6,6 @@ html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
@@ -333,8 +329,9 @@ body {
|
||||
font-family: "Roboto", "Helvetica", sans-serif;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
color: #373a3c;
|
||||
background-color: #fff;
|
||||
margin: 0;
|
||||
background: #E4E4E4;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
@@ -1296,10 +1293,6 @@ html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
@@ -1619,14 +1612,6 @@ html {
|
||||
-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 {
|
||||
outline: none !important;
|
||||
}
|
||||
@@ -9084,11 +9069,12 @@ strong {
|
||||
|
||||
.close-icon {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
display: none;
|
||||
justify-content: flex-end;
|
||||
padding: 13px;
|
||||
transition: opacity 250ms ease;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon img {
|
||||
@@ -10373,7 +10359,7 @@ All mobile class names should be prefixed by m- */
|
||||
}
|
||||
|
||||
.paid:not(.paid-over) .status-icon {
|
||||
padding-top: 54px;
|
||||
padding-top: 45px;
|
||||
}
|
||||
|
||||
.paid:not(.paid-over) .button-wrapper {
|
||||
|
||||
@@ -32,7 +32,6 @@ function changeCurrency(currency) {
|
||||
}
|
||||
|
||||
function onDataCallback(jsonData) {
|
||||
|
||||
var newStatus = jsonData.status;
|
||||
|
||||
if (newStatus === "complete" ||
|
||||
@@ -123,20 +122,6 @@ $(document).ready(function () {
|
||||
// initialize
|
||||
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
|
||||
if (srvModel.expirationSeconds > 0) {
|
||||
progressStart(srvModel.maxTimeSeconds); // Progress bar
|
||||
@@ -147,7 +132,19 @@ $(document).ready(function () {
|
||||
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() {
|
||||
$("#emailAddressView").removeClass("active");
|
||||
$("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