Merge branch 'lepipele-dev-lepi'

This commit is contained in:
nicolas.dorier
2018-03-03 15:06:58 -05:00
4 changed files with 367 additions and 294 deletions

View File

@@ -1,4 +1,5 @@
@model PaymentModel
@addTagHelper *, Meziantou.AspNetCore.BundleTagHelpers
@model PaymentModel
@{
Layout = null;
ViewData["Title"] = "Payment";
@@ -12,18 +13,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>BTCPay Invoice</title>
<link href="~/vendor/font-awesome/css/font-awesome.css" rel="stylesheet" />
<link href="~/css/css.css" rel="stylesheet" type="text/css">
<link href="~/css/normalizer.css" rel="stylesheet" type="text/css">
<bundle name="wwwroot/bundles/checkout-bundle.min.css" />
<script type="text/javascript">
@Model.ToJSVariableModel("srvModel")
</script>
<script src="~/vendor/clipboard.js/clipboard.js"></script>
<script src="~/vendor/jquery/jquery.js"></script>
<script src="~/js/vue.min.js" type="text/javascript"></script>
<script src="~/js/vue-qrcode.js" type="text/javascript"></script>
<script src="~/js/core.js" type="text/javascript" defer="defer"></script>
<bundle name="wwwroot/bundles/checkout-bundle.min.js" />
</head>
<body style="background: #E4E4E4">
<noscript>
@@ -72,7 +68,7 @@
<bp-spinner>
<svg xml:space="preserve" style="enable-background:new 0 0 50 50;" version="1.1" viewBox="0 0 50 50" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px">
<path d="M11.1,29.6c-0.5-1.5-0.8-3-0.8-4.6c0-8.1,6.6-14.7,14.7-14.7S39.7,16.9,39.7,25c0,1.6-0.3,3.2-0.8,4.6l6.1,2c0.7-2.1,1.1-4.3,1.1-6.6c0-11.7-9.5-21.2-21.2-21.2S3.8,13.3,3.8,25c0,2.3,0.4,4.5,1.1,6.6L11.1,29.6z"></path>
</svg>
</svg>
</bp-spinner>
</div>
<div class="timer-row__message">
@@ -88,6 +84,26 @@
</div>
</div>
<div class="order-details">
@if (Model.AvailableCryptos.Count > 1)
{
<div class="currency-selection">
<div class="single-item-order__left">
<div style="font-weight: 600;">
Currency Selection
</div>
</div>
<div class="single-item-order__right">
<div>
@foreach (var crypto in Model.AvailableCryptos)
{
<a href="@crypto.Link" onclick="return changeCurrency('@crypto.PaymentMethodId');">
<img style="height:32px; margin-left:5px;" alt="@crypto.PaymentMethodId" src="@crypto.CryptoImage" />
</a>
}
</div>
</div>
</div>
}
<!---->
<div class="single-item-order buyerTotalLine">
<div class="single-item-order__left">
@@ -163,6 +179,13 @@
</a>
</div>
</div>
<div class="payment__spinner">
<bp-spinner>
<svg xml:space="preserve" style="enable-background:new 0 0 50 50;" version="1.1" viewBox="0 0 50 50" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px">
<path d="M11.1,29.6c-0.5-1.5-0.8-3-0.8-4.6c0-8.1,6.6-14.7,14.7-14.7S39.7,16.9,39.7,25c0,1.6-0.3,3.2-0.8,4.6l6.1,2c0.7-2.1,1.1-4.3,1.1-6.6c0-11.7-9.5-21.2-21.2-21.2S3.8,13.3,3.8,25c0,2.3,0.4,4.5,1.1,6.6L11.1,29.6z"></path>
</svg>
</bp-spinner>
</div>
<div class="bp-view payment manual-flow enter-contact-email active" id="emailAddressView">
<form class="manual__step-one refund-address-form contact-email-form" id="emailAddressForm" name="emailAddressForm" novalidate="">
<div class="manual__step-one__header">
@@ -600,23 +623,27 @@
</div>
</div>
</div>
<div class="footer">
<div class="footer__item no-hover" style="opacity: 1; padding-left: 0; max-height: 21px;">
@if(Model.AvailableCryptos.Count > 1)
{
<div style="text-align:center">Accepted here</div>
<div style="text-align:center">
@foreach(var crypto in Model.AvailableCryptos)
{
<a style="text-decoration:none;" href="@crypto.Link" onclick="srvModel.paymentMethodId='@crypto.PaymentMethodId'; fetchStatus(); return false;"><img style="height:32px; margin-right:5px; margin-left:5px;" alt="@crypto.PaymentMethodId" src="@crypto.CryptoImage" /></a>
}
</div>
}
</div>
</div>
</div>
</div>
</div>
</invoice>
<script type="text/javascript">
// TODO: Move all logic from core.js to Vue controller
Vue.config.ignoredElements = [
'line-items',
'low-fee-timeline',
// Ignoring custom HTML5 elements, eg: bp-spinner
/^bp-/
];
var checkoutCtrl = new Vue({
el: '#checkoutCtrl',
components: {
qrcode: VueQr
},
data: {
srvModel: srvModel
}
});
</script>
</body>
</html>

View File

@@ -27,5 +27,23 @@
"wwwroot/vendor/jquery-validate/jquery.validate.js",
"wwwroot/vendor/jquery-validate-unobtrusive/jquery.validate.unobtrusive.js"
]
},
{
"outputFileName": "wwwroot/bundles/checkout-bundle.min.css",
"inputFiles": [
"wwwroot/vendor/font-awesome/css/font-awesome.css",
"wwwroot/css/css.css",
"wwwroot/css/normalizer.css"
]
},
{
"outputFileName": "wwwroot/bundles/checkout-bundle.min.js",
"inputFiles": [
"wwwroot/vendor/clipboard.js/clipboard.js",
"wwwroot/vendor/jquery/jquery.js",
"wwwroot/js/vue.min.js",
"wwwroot/js/vue-qrcode.js",
"wwwroot/js/core.js"
]
}
]

View File

@@ -8411,6 +8411,17 @@ strong {
float: right;
}
.currency-selection {
border-bottom: 1px solid #E9E9E9;
position: relative;
padding: 4px 15px;
display: flex;
font-weight: 300;
color: #565D6E;
letter-spacing: .45px;
background: #fff;
}
.single-item-order {
position: relative;
padding: 15px;
@@ -10487,6 +10498,7 @@ All mobile class names should be prefixed by m- */
min-height: 575px;
}
.paid .currency-selection,
.expired .order-details,
.archived .order-details {
display: none;
@@ -10881,6 +10893,20 @@ bp-spinner {
display: flex;
}
.payment__spinner {
margin-top: 140px;
display: none;
}
.payment__spinner > bp-spinner > svg {
margin: auto auto;
height: 60px;
width: 60px;
fill: gray;
animation: spin 0.55s linear infinite;
opacity: .85;
}
bp-refund-address.ng-valid .bitcoin-logo {
opacity: 1;
margin-left: 0;

View File

@@ -1,156 +1,4 @@
/* 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
*/
// TODO: Vue controller... complete migrate to it for binding, animations can stay in jQuery
Vue.config.ignoredElements = [
'line-items',
'low-fee-timeline',
// Ignoring custom HTML5 elements, eg: bp-spinner
/^bp-/
];
var checkoutCtrl = new Vue({
el: '#checkoutCtrl',
components: {
qrcode: VueQr
},
data: {
srvModel: srvModel
}
});
var display = $(".timer-row__time-left"); // Timer container
// check if the Document expired
if (srvModel.expirationSeconds > 0) {
progressStart(srvModel.maxTimeSeconds); // Progress bar
startTimer(srvModel.expirationSeconds, display); // Timer
if (!validateEmail(srvModel.customerEmail))
emailForm(); // Email form Display
else
hideEmailForm();
}
function hideEmailForm() {
$("[role=document]").removeClass("enter-purchaser-email");
$("#emailAddressView").removeClass("active");
$("placeholder-refundEmail").html(srvModel.customerEmail);
// Remove Email mode
$(".modal-dialog").removeClass("enter-purchaser-email");
$("#scan").addClass("active");
}
// Email Form
// Setup Email mode
function emailForm() {
$(".modal-dialog").addClass("enter-purchaser-email");
$("#emailAddressForm .action-button").click(function () {
var emailAddress = $("#emailAddressFormInput").val();
if (validateEmail(emailAddress)) {
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").addClass("loading");
// Push the email to a server, once the reception is confirmed move on
srvModel.customerEmail = emailAddress;
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/UpdateCustomer";
$.ajax({
url: path,
type: "POST",
data: JSON.stringify({ Email: srvModel.customerEmail }),
contentType: "application/json; charset=utf-8"
}).done(function () {
hideEmailForm();
})
.fail(function (jqXHR, textStatus, errorThrown) {
})
.always(function () {
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").removeClass("loading");
});
} else {
$("#emailAddressForm").addClass("ng-touched ng-dirty ng-submitted ng-invalid");
}
});
}
/* =============== Even listeners =============== */
// Email
$("#emailAddressFormInput").change(function () {
if ($("#emailAddressForm").hasClass("ng-submitted")) {
$("#emailAddressForm").removeClass("ng-submitted");
}
});
// Scan/Copy Transitions
// Scan Tab
$("#scan-tab").click(function () {
if (!$(this).is(".active")) {
$(this).addClass("active");
}
if ($("#copy-tab").is(".active")) {
$("#copy-tab").removeClass("active");
}
$(".payment-tabs__slider").removeClass("slide-right");
if (!$("#scan").is(".active")) {
$("#copy").hide();
$("#copy").removeClass("active");
$("#scan").show();
$("#scan").addClass("active");
}
});
// Main Copy tab
$("#copy-tab").click(function () {
if (!$(this).is(".active")) {
$(this).addClass("active");
}
if ($("#scan-tab").is(".active")) {
$("#scan-tab").removeClass("active");
}
if (!$(".payment-tabs__slider").is("slide-right")) {
$(".payment-tabs__slider").addClass("slide-right");
}
if (!$("#copy").is(".active")) {
$("#copy").show();
$("#copy").addClass("active");
$("#scan").hide();
$("#scan").removeClass("active");
}
});
// Payment received
// Should connect using webhook ?
// If notification received
onDataCallback(srvModel);
// public methods
function onDataCallback(jsonData) {
var newStatus = jsonData.status;
@@ -193,10 +41,26 @@ function onDataCallback(jsonData) {
window.parent.postMessage({ "invoiceId": srvModel.invoiceId, "status": newStatus }, "*");
}
// restoring qr code view only when currency is switched
if (jsonData.paymentMethodId == srvModel.paymentMethodId) {
$("#scan").show();
$(".payment__spinner").hide();
}
// updating ui
checkoutCtrl.srvModel = jsonData;
}
function changeCurrency(currency) {
if (srvModel.paymentMethodId != currency) {
$("#scan").hide();
$(".payment__spinner").show();
srvModel.paymentMethodId = currency;
fetchStatus();
}
return false;
}
function fetchStatus() {
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/" + srvModel.paymentMethodId + "/status";
$.ajax({
@@ -209,123 +73,261 @@ function fetchStatus() {
});
}
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
if (supportsWebSockets) {
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status/ws";
path = path.replace("https://", "wss://");
path = path.replace("http://", "ws://");
try {
var socket = new WebSocket(path);
socket.onmessage = function (e) {
fetchStatus();
};
}
catch (e) {
console.error("Error while connecting to websocket for invoice notifications");
}
}
// private methods
$(document).ready(function () {
// initialize
onDataCallback(srvModel);
var watcher = setInterval(function () {
fetchStatus();
}, 2000);
/* 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
*/
var display = $(".timer-row__time-left"); // Timer container
// check if the Document expired
if (srvModel.expirationSeconds > 0) {
progressStart(srvModel.maxTimeSeconds); // Progress bar
startTimer(srvModel.expirationSeconds, display); // Timer
if (!validateEmail(srvModel.customerEmail))
emailForm(); // Email form Display
else
hideEmailForm();
}
function hideEmailForm() {
$("[role=document]").removeClass("enter-purchaser-email");
$("#emailAddressView").removeClass("active");
$("placeholder-refundEmail").html(srvModel.customerEmail);
// Remove Email mode
$(".modal-dialog").removeClass("enter-purchaser-email");
$("#scan").addClass("active");
}
// Email Form
// Setup Email mode
function emailForm() {
$(".modal-dialog").addClass("enter-purchaser-email");
$("#emailAddressForm .action-button").click(function () {
var emailAddress = $("#emailAddressFormInput").val();
if (validateEmail(emailAddress)) {
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").addClass("loading");
// Push the email to a server, once the reception is confirmed move on
srvModel.customerEmail = emailAddress;
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/UpdateCustomer";
$.ajax({
url: path,
type: "POST",
data: JSON.stringify({ Email: srvModel.customerEmail }),
contentType: "application/json; charset=utf-8"
}).done(function () {
hideEmailForm();
})
.fail(function (jqXHR, textStatus, errorThrown) {
})
.always(function () {
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").removeClass("loading");
});
} else {
$("#emailAddressForm").addClass("ng-touched ng-dirty ng-submitted ng-invalid");
}
});
}
/* =============== Even listeners =============== */
// Email
$("#emailAddressFormInput").change(function () {
if ($("#emailAddressForm").hasClass("ng-submitted")) {
$("#emailAddressForm").removeClass("ng-submitted");
}
});
// Scan/Copy Transitions
// Scan Tab
$("#scan-tab").click(function () {
if (!$(this).is(".active")) {
$(this).addClass("active");
}
if ($("#copy-tab").is(".active")) {
$("#copy-tab").removeClass("active");
}
$(".payment-tabs__slider").removeClass("slide-right");
if (!$("#scan").is(".active")) {
$("#copy").hide();
$("#copy").removeClass("active");
$("#scan").show();
$("#scan").addClass("active");
}
});
// Main Copy tab
$("#copy-tab").click(function () {
if (!$(this).is(".active")) {
$(this).addClass("active");
}
if ($("#scan-tab").is(".active")) {
$("#scan-tab").removeClass("active");
}
if (!$(".payment-tabs__slider").is("slide-right")) {
$(".payment-tabs__slider").addClass("slide-right");
}
if (!$("#copy").is(".active")) {
$("#copy").show();
$("#copy").addClass("active");
$("#scan").hide();
$("#scan").removeClass("active");
}
});
// Payment received
// Should connect using webhook ?
// If notification received
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
if (supportsWebSockets) {
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status/ws";
path = path.replace("https://", "wss://");
path = path.replace("http://", "ws://");
try {
var socket = new WebSocket(path);
socket.onmessage = function (e) {
fetchStatus();
};
}
catch (e) {
console.error("Error while connecting to websocket for invoice notifications");
}
}
var watcher = setInterval(function () {
fetchStatus();
}, 2000);
$(".menu__item").click(function () {
$(".menu__scroll .menu__item").removeClass("selected");
$(this).addClass("selected");
language();
$(".selector span").text($(".selected").text());
// function to load contents in different language should go there
});
// Validate Email address
function validateEmail(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);
}
// Expand Line-Items
$(".buyerTotalLine").click(function () {
$("line-items").toggleClass("expanded");
$(".buyerTotalLine").toggleClass("expanded");
$(".single-item-order__right__btc-price__chevron").toggleClass("expanded");
});
// Timer Countdown
function startTimer(duration, display) {
var timer = duration, minutes, seconds;
var timeout = setInterval(function () {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.text(minutes + ":" + seconds);
if (--timer < 0) {
clearInterval(timeout);
}
}, 1000);
}
// Progress bar
function progressStart(timerMax) {
var end = new Date(); // Setup Time Variable, should come from server
end.setSeconds(end.getSeconds() + srvModel.expirationSeconds);
timerMax *= 1000; // Usually 15 minutes = 9000 second= 900000 ms
var timeoutVal = Math.floor(timerMax / 100); // Timeout calc
animateUpdate(); //Launch it
function updateProgress(percentage) {
$('.timer-row__progress-bar').css("width", percentage + "%");
}
function animateUpdate() {
var now = new Date();
var timeDiff = end.getTime() - now.getTime();
var perc = 100 - Math.round(timeDiff / timerMax * 100);
if (perc === 75 && (status === "paidPartial" || status === "new")) {
$(".timer-row").addClass("expiring-soon");
$(".timer-row__message span").html("Invoice expiring soon ...");
updateProgress(perc);
}
if (perc <= 100) {
updateProgress(perc);
setTimeout(animateUpdate, timeoutVal);
}
if (perc >= 100 && status === "expired") {
onDataCallback(status);
}
}
}
// Manual Copy
// Amount
var copyAmount = new Clipboard('.manual-box__amount__value', {
target: function () {
var $el = $(".manual-box__amount__value");
$el.removeClass("copy-cursor").addClass("copied");
setTimeout(function () { $el.removeClass("copied").addClass("copy-cursor"); }, 500);
return document.querySelector('.manual-box__amount__value span');
}
});
// Address
var copyAddress = new Clipboard('.manual-box__address__value', {
target: function () {
var $elm = $(".manual-box__address__value");
$elm.removeClass("copy-cursor").addClass("copied");
setTimeout(function () { $elm.removeClass("copied").addClass("copy-cursor"); }, 500);
return document.querySelector('.manual-box__address__value .manual-box__address__wrapper .manual-box__address__wrapper__value');
}
});
// Disable enter key
$(document).keypress(
function (event) {
if (event.which === '13') {
event.preventDefault();
}
}
);
$(".menu__item").click(function () {
$(".menu__scroll .menu__item").removeClass("selected");
$(this).addClass("selected");
language();
$(".selector span").text($(".selected").text());
// function to load contents in different language should go there
});
// Validate Email address
function validateEmail(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);
}
// Expand Line-Items
$(".buyerTotalLine").click(function () {
$("line-items").toggleClass("expanded");
$(".buyerTotalLine").toggleClass("expanded");
$(".single-item-order__right__btc-price__chevron").toggleClass("expanded");
});
// Timer Countdown
function startTimer(duration, display) {
var timer = duration, minutes, seconds;
var timeout = setInterval(function () {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.text(minutes + ":" + seconds);
if (--timer < 0) {
clearInterval(timeout);
}
}, 1000);
}
// Progress bar
function progressStart(timerMax) {
var end = new Date(); // Setup Time Variable, should come from server
end.setSeconds(end.getSeconds() + srvModel.expirationSeconds);
timerMax *= 1000; // Usually 15 minutes = 9000 second= 900000 ms
var timeoutVal = Math.floor(timerMax / 100); // Timeout calc
animateUpdate(); //Launch it
function updateProgress(percentage) {
$('.timer-row__progress-bar').css("width", percentage + "%");
}
function animateUpdate() {
var now = new Date();
var timeDiff = end.getTime() - now.getTime();
var perc = 100 - Math.round(timeDiff / timerMax * 100);
if (perc === 75 && (status === "paidPartial" || status === "new")) {
$(".timer-row").addClass("expiring-soon");
$(".timer-row__message span").html("Invoice expiring soon ...");
updateProgress(perc);
}
if (perc <= 100) {
updateProgress(perc);
setTimeout(animateUpdate, timeoutVal);
}
if (perc >= 100 && status === "expired") {
onDataCallback(status);
}
}
}
// Manual Copy
// Amount
var copyAmount = new Clipboard('.manual-box__amount__value', {
target: function () {
var $el = $(".manual-box__amount__value");
$el.removeClass("copy-cursor").addClass("copied");
setTimeout(function () { $el.removeClass("copied").addClass("copy-cursor"); }, 500);
return document.querySelector('.manual-box__amount__value span');
}
});
// Address
var copyAddress = new Clipboard('.manual-box__address__value', {
target: function () {
var $elm = $(".manual-box__address__value");
$elm.removeClass("copy-cursor").addClass("copied");
setTimeout(function () { $elm.removeClass("copied").addClass("copy-cursor"); }, 500);
return document.querySelector('.manual-box__address__value .manual-box__address__wrapper .manual-box__address__wrapper__value');
}
});
// Disable enter key
$(document).keypress(
function (event) {
if (event.which === '13') {
event.preventDefault();
}
}
);