mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 14:04:26 +01:00
Allow overriding UI of checkout in payment handler (#992)
This commit is contained in:
committed by
Nicolas Dorier
parent
989a7b863e
commit
43ee22f965
@@ -167,7 +167,6 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
//TODO: abstract
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
@@ -297,6 +296,7 @@ namespace BTCPayServer.Controllers
|
||||
};
|
||||
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto);
|
||||
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
||||
model.PaymentMethodId = paymentMethodId.ToString();
|
||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||
model.TimeLeft = expiration.PrettyPrint();
|
||||
|
||||
@@ -5,8 +5,15 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
public class CheckoutUIPaymentMethodSettings
|
||||
{
|
||||
public string ExtensionPartial { get; set; }
|
||||
public string CheckoutBodyVueComponentName { get; set; }
|
||||
public string NoScriptPartialName { get; set; }
|
||||
}
|
||||
public class PaymentModel
|
||||
{
|
||||
public CheckoutUIPaymentMethodSettings UISettings;
|
||||
public class AvailableCrypto
|
||||
{
|
||||
public string PaymentMethodId { get; set; }
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace BTCPayServer.Payments
|
||||
Money amount, PaymentMethodId paymentMethodId);
|
||||
|
||||
IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
|
||||
CheckoutUIPaymentMethodSettings GetCheckoutUISettings();
|
||||
}
|
||||
|
||||
public interface IPaymentMethodHandler<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler
|
||||
@@ -93,6 +94,11 @@ namespace BTCPayServer.Payments
|
||||
throw new NotSupportedException("Invalid supportedPaymentMethod");
|
||||
}
|
||||
|
||||
public virtual CheckoutUIPaymentMethodSettings GetCheckoutUISettings()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
object IPaymentMethodHandler.PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store,
|
||||
BTCPayNetworkBase network)
|
||||
{
|
||||
|
||||
@@ -99,12 +99,10 @@
|
||||
<div class="single-item-order__right__btc-price" v-else>
|
||||
<span>{{ srvModel.btcDue }} {{ srvModel.cryptoCode }}</span>
|
||||
</div>
|
||||
|
||||
<div class="single-item-order__right__ex-rate" v-if="srvModel.orderAmountFiat">
|
||||
<div class="single-item-order__right__ex-rate" v-if="srvModel.orderAmountFiat && srvModel.cryptoCode">
|
||||
1 {{ srvModel.cryptoCodeSrv }} = {{ srvModel.rate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="fa fa-angle-double-down"></span>
|
||||
<span class="fa fa-angle-double-up"></span>
|
||||
</div>
|
||||
@@ -128,7 +126,7 @@
|
||||
<span>{{$t("Network Cost")}}</span>
|
||||
</div>
|
||||
<div class="line-items__item__value">
|
||||
<span v-if="srvModel.IsMultiCurrency">
|
||||
<span v-if="srvModel.isMultiCurrency">
|
||||
{{ srvModel.networkFee }} {{ srvModel.cryptoCode }}
|
||||
</span>
|
||||
<span v-else>
|
||||
@@ -148,7 +146,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</line-items>
|
||||
<div class="payment-tabs">
|
||||
<div class="payment-tabs" v-if="!srvModel.uiSettings || !srvModel.uiSettings.checkoutBodyVueComponentName">
|
||||
<div class="payment-tabs__tab active" id="scan-tab">
|
||||
<span>{{$t("Scan")}}</span>
|
||||
</div>
|
||||
@@ -194,6 +192,14 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div v-if="showPaymentUI">
|
||||
<component
|
||||
v-if="srvModel.uiSettings && srvModel.uiSettings.checkoutBodyVueComponentName"
|
||||
v-bind:srv-model="srvModel"
|
||||
v-bind:is="srvModel.uiSettings.checkoutBodyVueComponentName">
|
||||
</component>
|
||||
|
||||
<template v-else>
|
||||
<div class="bp-view payment scan" id="scan">
|
||||
<div class="wrapBtnGroup" v-bind:class="{ invisible: lndModel === null || !scanDisplayQr }">
|
||||
<div class="btnGroupLnd">
|
||||
@@ -326,15 +332,14 @@
|
||||
v-on:load="onLoadIframe"
|
||||
style="height: 100%; position: fixed; top: 0; width: 100%; left: 0;"
|
||||
sandbox="allow-scripts allow-forms allow-popups allow-same-origin"
|
||||
:src="url"></iframe>
|
||||
|
||||
:src="url">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
</coinswitch>
|
||||
}
|
||||
|
||||
@if(Model.ChangellyEnabled){
|
||||
|
||||
@if (Model.ChangellyEnabled)
|
||||
{
|
||||
<changelly inline-template
|
||||
v-if="!srvModel.coinSwitchEnabled || selectedThirdPartyProcessor === 'changelly'"
|
||||
:merchant-id="srvModel.changellyMerchantId"
|
||||
@@ -382,6 +387,8 @@
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="bp-view" id="paid">
|
||||
<div class="status-block">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@addTagHelper *, BundlerMinifier.TagHelpers
|
||||
@inject BTCPayServer.Services.LanguageService langService
|
||||
@using Newtonsoft.Json
|
||||
@using Newtonsoft.Json.Linq
|
||||
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
|
||||
@model PaymentModel
|
||||
@{
|
||||
Layout = null;
|
||||
@@ -152,7 +151,7 @@
|
||||
return availableLanguages.indexOf(languageCode) >= 0;
|
||||
}
|
||||
|
||||
const i18n = new VueI18next(i18next);
|
||||
var i18n = new VueI18next(i18next);
|
||||
|
||||
// TODO: Move all logic from core.js to Vue controller
|
||||
Vue.config.ignoredElements = [
|
||||
@@ -161,7 +160,6 @@
|
||||
// Ignoring custom HTML5 elements, eg: bp-spinner
|
||||
/^bp-/
|
||||
];
|
||||
|
||||
var checkoutCtrl = new Vue({
|
||||
i18n: i18n,
|
||||
el: '#checkoutCtrl',
|
||||
@@ -184,9 +182,18 @@
|
||||
return this.srvModel.coinSwitchAmountMarkupPercentage
|
||||
? this.srvModel.btcDue * (1 + (this.srvModel.coinSwitchAmountMarkupPercentage / 100))
|
||||
: this.srvModel.btcDue;
|
||||
},
|
||||
showPaymentUI: function(){
|
||||
var disallowedStatuses = ["complete","confirmed" ,"paid", "expired", "invalid"];
|
||||
return (!this.srvModel.requiresRefundEmail || validateEmail(srvModel.customerEmail)) && disallowedStatuses.indexOf(this.srvModel.status) < 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@foreach (var paymentMethodHandler in PaymentMethodHandlerDictionary.Select(handler => handler.GetCheckoutUISettings()).Where(settings => settings != null))
|
||||
{
|
||||
<partial name="@paymentMethodHandler.ExtensionPartial" model="@Model"/>
|
||||
}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
<body>
|
||||
<h1>Pay with @Model.StoreName</h1>
|
||||
@if (Model.Status == "new")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Model.UISettings?.NoScriptPartialName))
|
||||
{
|
||||
<partial model="@Model" name="@Model.UISettings.NoScriptPartialName"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>
|
||||
<p>To complete payment, please send <b>@Model.BtcDue @Model.CryptoCode</b> to <b style="word-break: break-word;">@Model.BtcAddress</b></p>
|
||||
@@ -25,7 +31,7 @@
|
||||
<p>Peer Info: <b>@Model.PeerInfo</b></p>
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
@if (Model.AvailableCryptos.Count > 1)
|
||||
{
|
||||
<div>
|
||||
|
||||
@@ -1,6 +1,29 @@
|
||||
// TODO: Refactor... switch from jQuery to Vue.js
|
||||
// public methods
|
||||
function resetTabsSlider() {
|
||||
// Scan/Copy Transitions
|
||||
// Scan Tab
|
||||
$("#scan-tab").off().on("click", function () {
|
||||
resetTabsSlider();
|
||||
activateTab("#scan");
|
||||
});
|
||||
|
||||
// Copy tab
|
||||
$("#copy-tab").off().on("click", function () {
|
||||
resetTabsSlider();
|
||||
activateTab("#copy");
|
||||
|
||||
$("#tabsSlider").addClass("slide-copy");
|
||||
});
|
||||
|
||||
// Altcoins tab
|
||||
$("#altcoins-tab").off().on("click", function () {
|
||||
resetTabsSlider();
|
||||
activateTab("#altcoins");
|
||||
|
||||
$("#tabsSlider").addClass("slide-altcoins");
|
||||
});
|
||||
|
||||
$("#tabsSlider").removeClass("slide-copy");
|
||||
$("#tabsSlider").removeClass("slide-altcoins");
|
||||
|
||||
@@ -27,6 +50,17 @@ function changeCurrency(currency) {
|
||||
checkoutCtrl.scanDisplayQr = "";
|
||||
srvModel.paymentMethodId = currency;
|
||||
fetchStatus();
|
||||
setTimeout(function(){
|
||||
resetTabsSlider();
|
||||
if ($("#tabsSlider").hasClass("slide-copy")) {
|
||||
activateTab("#copy");
|
||||
} else if ($("#tabsSlider").hasClass("slide-altcoins")) {
|
||||
activateTab("#altcoins");
|
||||
} else {
|
||||
activateTab("#scan");
|
||||
}
|
||||
},50);
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -162,6 +196,14 @@ function onlyExpandLineItems() {
|
||||
}
|
||||
|
||||
|
||||
function activateTab(senderName) {
|
||||
$(senderName + "-tab").addClass("active");
|
||||
|
||||
$(senderName).show();
|
||||
$(senderName).addClass("active");
|
||||
}
|
||||
|
||||
|
||||
// private methods
|
||||
$(document).ready(function () {
|
||||
// initialize
|
||||
@@ -239,11 +281,7 @@ $(document).ready(function () {
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
/* =============== Even listeners =============== */
|
||||
|
||||
@@ -255,35 +293,8 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
|
||||
// Scan/Copy Transitions
|
||||
// Scan Tab
|
||||
$("#scan-tab").click(function () {
|
||||
resetTabsSlider();
|
||||
activateTab("#scan");
|
||||
});
|
||||
|
||||
// Copy tab
|
||||
$("#copy-tab").click(function () {
|
||||
resetTabsSlider();
|
||||
activateTab("#copy");
|
||||
|
||||
$("#tabsSlider").addClass("slide-copy");
|
||||
});
|
||||
|
||||
// Altcoins tab
|
||||
$("#altcoins-tab").click(function () {
|
||||
resetTabsSlider();
|
||||
activateTab("#altcoins");
|
||||
|
||||
$("#tabsSlider").addClass("slide-altcoins");
|
||||
});
|
||||
|
||||
function activateTab(senderName) {
|
||||
$(senderName + "-tab").addClass("active");
|
||||
|
||||
$(senderName).show();
|
||||
$(senderName).addClass("active");
|
||||
}
|
||||
|
||||
// Payment received
|
||||
// Should connect using webhook ?
|
||||
@@ -423,3 +434,9 @@ $(document).ready(function () {
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user