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:
Rockstar Developer
2018-11-09 01:09:09 -06:00
committed by Nicolas Dorier
parent c9c7316b7d
commit aa1ac3da50
8 changed files with 205 additions and 70 deletions

View File

@@ -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) &&

View File

@@ -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; }

View File

@@ -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">
&#10006;
</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>

View File

@@ -27,10 +27,20 @@
<link href="@Model.CustomCSSLink" rel="stylesheet" /> <link href="@Model.CustomCSSLink" rel="stylesheet" />
} }
@if (Model.IsModal)
{
<style type="text/css"> <style type="text/css">
body {
background: rgba(55, 58, 60, 0.4);
}
.close-icon {
display: flex;
}
</style> </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,7 +63,7 @@
</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">
@@ -103,6 +113,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</invoice> </invoice>
<script type="text/javascript"> <script type="text/javascript">
var storeDefaultLang = '@Model.DefaultLang'; var storeDefaultLang = '@Model.DefaultLang';
@@ -165,9 +176,10 @@
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>

View File

@@ -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 {

View File

@@ -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);

View 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
};
})();

View 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>