mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 14:04:12 +01:00
Allow overriding UI of checkout in payment handler (#992)
This commit is contained in:
parent
989a7b863e
commit
43ee22f965
7 changed files with 296 additions and 246 deletions
|
@ -167,7 +167,6 @@ namespace BTCPayServer.Controllers
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: abstract
|
|
||||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId)
|
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId)
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||||
|
@ -297,6 +296,7 @@ namespace BTCPayServer.Controllers
|
||||||
};
|
};
|
||||||
|
|
||||||
paymentMethodHandler.PreparePaymentModel(model, dto);
|
paymentMethodHandler.PreparePaymentModel(model, dto);
|
||||||
|
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
||||||
model.PaymentMethodId = paymentMethodId.ToString();
|
model.PaymentMethodId = paymentMethodId.ToString();
|
||||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||||
model.TimeLeft = expiration.PrettyPrint();
|
model.TimeLeft = expiration.PrettyPrint();
|
||||||
|
|
|
@ -5,8 +5,15 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BTCPayServer.Models.InvoicingModels
|
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 class PaymentModel
|
||||||
{
|
{
|
||||||
|
public CheckoutUIPaymentMethodSettings UISettings;
|
||||||
public class AvailableCrypto
|
public class AvailableCrypto
|
||||||
{
|
{
|
||||||
public string PaymentMethodId { get; set; }
|
public string PaymentMethodId { get; set; }
|
||||||
|
|
|
@ -46,6 +46,7 @@ namespace BTCPayServer.Payments
|
||||||
Money amount, PaymentMethodId paymentMethodId);
|
Money amount, PaymentMethodId paymentMethodId);
|
||||||
|
|
||||||
IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
|
IEnumerable<PaymentMethodId> GetSupportedPaymentMethods();
|
||||||
|
CheckoutUIPaymentMethodSettings GetCheckoutUISettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPaymentMethodHandler<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler
|
public interface IPaymentMethodHandler<TSupportedPaymentMethod, TBTCPayNetwork> : IPaymentMethodHandler
|
||||||
|
@ -92,6 +93,11 @@ namespace BTCPayServer.Payments
|
||||||
|
|
||||||
throw new NotSupportedException("Invalid supportedPaymentMethod");
|
throw new NotSupportedException("Invalid supportedPaymentMethod");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual CheckoutUIPaymentMethodSettings GetCheckoutUISettings()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
object IPaymentMethodHandler.PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store,
|
object IPaymentMethodHandler.PreparePayment(ISupportedPaymentMethod supportedPaymentMethod, StoreData store,
|
||||||
BTCPayNetworkBase network)
|
BTCPayNetworkBase network)
|
||||||
|
|
|
@ -99,12 +99,10 @@
|
||||||
<div class="single-item-order__right__btc-price" v-else>
|
<div class="single-item-order__right__btc-price" v-else>
|
||||||
<span>{{ srvModel.btcDue }} {{ srvModel.cryptoCode }}</span>
|
<span>{{ srvModel.btcDue }} {{ srvModel.cryptoCode }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="single-item-order__right__ex-rate" v-if="srvModel.orderAmountFiat && srvModel.cryptoCode">
|
||||||
<div class="single-item-order__right__ex-rate" v-if="srvModel.orderAmountFiat">
|
|
||||||
1 {{ srvModel.cryptoCodeSrv }} = {{ srvModel.rate }}
|
1 {{ srvModel.cryptoCodeSrv }} = {{ srvModel.rate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="fa fa-angle-double-down"></span>
|
<span class="fa fa-angle-double-down"></span>
|
||||||
<span class="fa fa-angle-double-up"></span>
|
<span class="fa fa-angle-double-up"></span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -128,7 +126,7 @@
|
||||||
<span>{{$t("Network Cost")}}</span>
|
<span>{{$t("Network Cost")}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="line-items__item__value">
|
<div class="line-items__item__value">
|
||||||
<span v-if="srvModel.IsMultiCurrency">
|
<span v-if="srvModel.isMultiCurrency">
|
||||||
{{ srvModel.networkFee }} {{ srvModel.cryptoCode }}
|
{{ srvModel.networkFee }} {{ srvModel.cryptoCode }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
|
@ -148,7 +146,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</line-items>
|
</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">
|
<div class="payment-tabs__tab active" id="scan-tab">
|
||||||
<span>{{$t("Scan")}}</span>
|
<span>{{$t("Scan")}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -170,218 +168,227 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="payment-box">
|
<div class="payment-box">
|
||||||
<div class="bp-view payment manual-flow enter-contact-email active" id="emailAddressView">
|
<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="">
|
<form class="manual__step-one refund-address-form contact-email-form" id="emailAddressForm" name="emailAddressForm" novalidate="">
|
||||||
<div class="manual__step-one__header">
|
<div class="manual__step-one__header">
|
||||||
<span>{{$t("Contact and Refund Email")}}</span>
|
<span>{{$t("Contact and Refund Email")}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="manual__step-one__instructions">
|
<div class="manual__step-one__instructions">
|
||||||
<span class="initial-label">
|
<span class="initial-label">
|
||||||
<span>{{$t("Contact_Body")}}</span>
|
<span>{{$t("Contact_Body")}}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="submission-error-label">{{$t("Please enter a valid email address")}}</span>
|
<span class="submission-error-label">{{$t("Please enter a valid email address")}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<input class="bp-input email-input ng-pristine ng-invalid ng-touched" id="emailAddressFormInput" v-bind:placeholder="$t('Your email')" type="email">
|
<input class="bp-input email-input ng-pristine ng-invalid ng-touched" id="emailAddressFormInput" v-bind:placeholder="$t('Your email')" type="email">
|
||||||
<bp-loading-button>
|
<bp-loading-button>
|
||||||
<button type="submit" class="action-button" style="margin-top: 15px;">
|
<button type="submit" class="action-button" style="margin-top: 15px;">
|
||||||
<span class="button-text">{{$t("Continue")}}</span>
|
<span class="button-text">{{$t("Continue")}}</span>
|
||||||
<div class="loader-wrapper">
|
<div class="loader-wrapper">
|
||||||
<partial name="Checkout-Spinner" />
|
<partial name="Checkout-Spinner"/>
|
||||||
</div>
|
</div>
|
||||||
|
</button>
|
||||||
|
</bp-loading-button>
|
||||||
|
</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">
|
||||||
|
<button onclick="lndToggleBolt11()" v-bind:class="{ active: lndModel != null && lndModel.toggle === 0 }"
|
||||||
|
v-bind:title="$t('BOLT 11 Invoice')">
|
||||||
|
{{$t("BOLT 11 Invoice")}}
|
||||||
</button>
|
</button>
|
||||||
</bp-loading-button>
|
<button onclick="lndToggleNode()" v-bind:class="{ active: lndModel != null && lndModel.toggle === 1 }"
|
||||||
|
v-bind:title="$t('Node Info')">
|
||||||
|
{{$t("Node Info")}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div class="payment__scan">
|
||||||
</div>
|
<img v-bind:src="srvModel.cryptoImage" class="qr_currency_icon"
|
||||||
<div class="bp-view payment scan" id="scan">
|
v-if="scanDisplayQr"/>
|
||||||
<div class="wrapBtnGroup" v-bind:class="{ invisible: lndModel === null || !scanDisplayQr }">
|
<qrcode v-bind:value="scanDisplayQr" :options="{ width: 256, margin: 0, color: {dark:'#000', light:'#f5f5f7'} }" tag="svg"
|
||||||
<div class="btnGroupLnd">
|
v-if="scanDisplayQr">
|
||||||
<button onclick="lndToggleBolt11()" v-bind:class="{ active: lndModel != null && lndModel.toggle === 0 }"
|
</qrcode>
|
||||||
v-bind:title="$t('BOLT 11 Invoice')">
|
|
||||||
{{$t("BOLT 11 Invoice")}}
|
|
||||||
</button>
|
|
||||||
<button onclick="lndToggleNode()" v-bind:class="{ active: lndModel != null && lndModel.toggle === 1 }"
|
|
||||||
v-bind:title="$t('Node Info')">
|
|
||||||
{{$t("Node Info")}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="payment__scan">
|
|
||||||
<img v-bind:src="srvModel.cryptoImage" class="qr_currency_icon"
|
|
||||||
v-if="scanDisplayQr" />
|
|
||||||
<qrcode v-bind:value="scanDisplayQr" :options="{ width: 256, margin: 0, color: {dark:'#000', light:'#f5f5f7'} }" tag="svg"
|
|
||||||
v-if="scanDisplayQr">
|
|
||||||
</qrcode>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="payment__spinner qr_currency_icon" style="padding-right: 20px;">
|
<div class="payment__spinner qr_currency_icon" style="padding-right: 20px;">
|
||||||
<partial name="Checkout-Spinner" />
|
<partial name="Checkout-Spinner"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="payment__details__instruction__open-wallet" v-if="scanDisplayQr">
|
||||||
|
<a class="payment__details__instruction__open-wallet__btn action-button" v-bind:href="srvModel.invoiceBitcoinUrl">
|
||||||
|
<span>{{$t("Open in wallet")}}</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="payment__details__instruction__open-wallet" v-if="scanDisplayQr">
|
|
||||||
<a class="payment__details__instruction__open-wallet__btn action-button" v-bind:href="srvModel.invoiceBitcoinUrl">
|
|
||||||
<span>{{$t("Open in wallet")}}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bp-view payment manual-flow" id="copy">
|
<div class="bp-view payment manual-flow" id="copy">
|
||||||
<div class="manual__step-two__instructions">
|
<div class="manual__step-two__instructions">
|
||||||
<span i18n="">{{$t("CompletePay_Body", srvModel)}}</span>
|
<span i18n="">{{$t("CompletePay_Body", srvModel)}}</span>
|
||||||
</div>
|
|
||||||
<div class="copyLabelPopup">
|
|
||||||
<span>{{$t("Copied")}}</span>
|
|
||||||
</div>
|
|
||||||
<nav v-if="srvModel.isLightning" class="copyBox">
|
|
||||||
<div class="copySectionBox bottomBorder">
|
|
||||||
<label>{{$t("BOLT 11 Invoice")}}</label>
|
|
||||||
<div class="inputWithIcon _copyInput">
|
|
||||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.btcAddress" readonly="readonly" />
|
|
||||||
<img v-bind:src="srvModel.cryptoImage" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="separatorGem"></div>
|
<div class="copyLabelPopup">
|
||||||
<div class="copySectionBox">
|
<span>{{$t("Copied")}}</span>
|
||||||
<label>{{$t("Node Info")}}</label>
|
|
||||||
<div class="inputWithIcon _copyInput">
|
|
||||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.peerInfo" readonly="readonly" />
|
|
||||||
<img v-bind:src="srvModel.cryptoImage" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
<nav v-if="srvModel.isLightning" class="copyBox">
|
||||||
<nav v-else class="copyBox">
|
<div class="copySectionBox bottomBorder">
|
||||||
<div class="copySectionBox bottomBorder">
|
<label>{{$t("BOLT 11 Invoice")}}</label>
|
||||||
<label>{{$t("Amount")}}</label>
|
<div class="inputWithIcon _copyInput">
|
||||||
<div class="copyAmountText copy-cursor _copySpan">
|
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.btcAddress" readonly="readonly"/>
|
||||||
<span>{{srvModel.btcDue}}</span> {{ srvModel.cryptoCode }}
|
<img v-bind:src="srvModel.cryptoImage"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="separatorGem"></div>
|
||||||
<div class="separatorGem"></div>
|
<div class="copySectionBox">
|
||||||
<div class="copySectionBox">
|
<label>{{$t("Node Info")}}</label>
|
||||||
<label>{{$t("Address")}}</label>
|
<div class="inputWithIcon _copyInput">
|
||||||
<div class="inputWithIcon _copyInput">
|
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.peerInfo" readonly="readonly"/>
|
||||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.btcAddress" readonly="readonly" />
|
<img v-bind:src="srvModel.cryptoImage"/>
|
||||||
<img v-bind:src="srvModel.cryptoImage" />
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
@if (Model.ChangellyEnabled || Model.CoinSwitchEnabled)
|
|
||||||
{
|
|
||||||
<div id="altcoins" class="bp-view payment manual-flow">
|
|
||||||
<nav v-if="srvModel.isLightning">
|
|
||||||
<div class="manual__step-two__instructions">
|
|
||||||
<span>
|
|
||||||
{{$t("ConversionTab_Lightning")}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<nav v-else>
|
<nav v-else class="copyBox">
|
||||||
<div class="manual__step-two__instructions">
|
<div class="copySectionBox bottomBorder">
|
||||||
<span>
|
<label>{{$t("Amount")}}</label>
|
||||||
{{$t("ConversionTab_BodyTop", srvModel)}}
|
<div class="copyAmountText copy-cursor _copySpan">
|
||||||
<br /><br />
|
<span>{{srvModel.btcDue}}</span> {{ srvModel.cryptoCode }}
|
||||||
{{$t("ConversionTab_BodyDesc", srvModel)}}
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<center>
|
<div class="separatorGem"></div>
|
||||||
|
<div class="copySectionBox">
|
||||||
@if (Model.CoinSwitchEnabled && Model.ChangellyEnabled)
|
<label>{{$t("Address")}}</label>
|
||||||
{
|
<div class="inputWithIcon _copyInput">
|
||||||
<template v-if="!selectedThirdPartyProcessor">
|
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.btcAddress" readonly="readonly"/>
|
||||||
<button v-on:click="selectedThirdPartyProcessor = 'coinswitch'" class="action-button">
|
<img v-bind:src="srvModel.cryptoImage"/>
|
||||||
{{$t("Pay with CoinSwitch")}}
|
</div>
|
||||||
</button>
|
</div>
|
||||||
<button v-on:click="selectedThirdPartyProcessor = 'changelly'" class="action-button">
|
</nav>
|
||||||
{{$t("Pay with Changelly")}}
|
</div>
|
||||||
</button>
|
@if (Model.ChangellyEnabled || Model.CoinSwitchEnabled)
|
||||||
</template>
|
{
|
||||||
}
|
<div id="altcoins" class="bp-view payment manual-flow">
|
||||||
|
<nav v-if="srvModel.isLightning">
|
||||||
@if (Model.CoinSwitchEnabled)
|
<div class="manual__step-two__instructions">
|
||||||
{
|
<span>
|
||||||
<coinswitch inline-template
|
{{$t("ConversionTab_Lightning")}}
|
||||||
v-if="!srvModel.changellyEnabled || selectedThirdPartyProcessor === 'coinswitch'"
|
</span>
|
||||||
:mode="srvModel.coinSwitchMode"
|
</div>
|
||||||
:merchant-id="srvModel.coinSwitchMerchantId"
|
</nav>
|
||||||
:to-currency="srvModel.paymentMethodId"
|
<nav v-else>
|
||||||
:to-currency-due="coinswitchAmountDue"
|
<div class="manual__step-two__instructions">
|
||||||
:autoload="selectedThirdPartyProcessor === 'coinswitch'"
|
<span>
|
||||||
:to-currency-address="srvModel.btcAddress">
|
{{$t("ConversionTab_BodyTop", srvModel)}}
|
||||||
<div>
|
<br/><br/>
|
||||||
<a v-on:click="openDialog($event)" :href="url" class="action-button" v-show="url && !opened">
|
{{$t("ConversionTab_BodyDesc", srvModel)}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<center>
|
||||||
|
|
||||||
|
@if (Model.CoinSwitchEnabled && Model.ChangellyEnabled)
|
||||||
|
{
|
||||||
|
<template v-if="!selectedThirdPartyProcessor">
|
||||||
|
<button v-on:click="selectedThirdPartyProcessor = 'coinswitch'" class="action-button">
|
||||||
{{$t("Pay with CoinSwitch")}}
|
{{$t("Pay with CoinSwitch")}}
|
||||||
</a>
|
</button>
|
||||||
|
<button v-on:click="selectedThirdPartyProcessor = 'changelly'" class="action-button">
|
||||||
@if (Model.ChangellyEnabled)
|
|
||||||
{
|
|
||||||
<button v-show="!opened" v-on:click="$parent.selectedThirdPartyProcessor = 'changelly'" class="btn-link mt-2">
|
|
||||||
{{$t("Pay with Changelly")}}
|
{{$t("Pay with Changelly")}}
|
||||||
</button>
|
</button>
|
||||||
}
|
</template>
|
||||||
|
}
|
||||||
<iframe
|
|
||||||
v-if="showInlineIFrame"
|
|
||||||
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>
|
|
||||||
|
|
||||||
</div>
|
@if (Model.CoinSwitchEnabled)
|
||||||
|
{
|
||||||
</coinswitch>
|
<coinswitch inline-template
|
||||||
}
|
v-if="!srvModel.changellyEnabled || selectedThirdPartyProcessor === 'coinswitch'"
|
||||||
|
:mode="srvModel.coinSwitchMode"
|
||||||
@if(Model.ChangellyEnabled){
|
:merchant-id="srvModel.coinSwitchMerchantId"
|
||||||
|
:to-currency="srvModel.paymentMethodId"
|
||||||
<changelly inline-template
|
:to-currency-due="coinswitchAmountDue"
|
||||||
v-if="!srvModel.coinSwitchEnabled || selectedThirdPartyProcessor === 'changelly'"
|
:autoload="selectedThirdPartyProcessor === 'coinswitch'"
|
||||||
:merchant-id="srvModel.changellyMerchantId"
|
:to-currency-address="srvModel.btcAddress">
|
||||||
:store-id="srvModel.storeId"
|
<div>
|
||||||
:to-currency="srvModel.paymentMethodId"
|
<a v-on:click="openDialog($event)" :href="url" class="action-button" v-show="url && !opened">
|
||||||
:to-currency-due="srvModel.changellyAmountDue"
|
|
||||||
:to-currency-address="srvModel.btcAddress">
|
|
||||||
<div class="changelly-component">
|
|
||||||
<div class="changelly-component-dropdown-holder" v-show="prettyDropdownInstance">
|
|
||||||
<select
|
|
||||||
v-model="selectedFromCurrency"
|
|
||||||
:disabled="isLoading"
|
|
||||||
v-on:change="onCurrencyChange($event)"
|
|
||||||
ref="changellyCurrenciesDropdown">
|
|
||||||
<option value="">{{$t("ConversionTab_CurrencyList_Select_Option")}}</option>
|
|
||||||
<option v-for="currency of currencies"
|
|
||||||
:data-prefix="'<img src=\''+currency.image+'\'/>'"
|
|
||||||
:value="currency.name">
|
|
||||||
{{currency.fullName}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<a v-on:click="openDialog($event)" :href="url" class="action-button" v-show="url">
|
|
||||||
{{$t("Pay with Changelly")}}
|
|
||||||
</a>
|
|
||||||
@if (Model.CoinSwitchEnabled)
|
|
||||||
{
|
|
||||||
<button v-on:click="$parent.selectedThirdPartyProcessor = 'coinswitch'" class="btn-link mt-2">
|
|
||||||
{{$t("Pay with CoinSwitch")}}
|
{{$t("Pay with CoinSwitch")}}
|
||||||
</button>
|
</a>
|
||||||
}
|
|
||||||
<button class="retry-button" v-if="calculateError" v-on:click="retry('calculateAmount')">
|
@if (Model.ChangellyEnabled)
|
||||||
{{$t("ConversionTab_CalculateAmount_Error")}}
|
{
|
||||||
</button>
|
<button v-show="!opened" v-on:click="$parent.selectedThirdPartyProcessor = 'changelly'" class="btn-link mt-2">
|
||||||
<button class="retry-button" v-if="currenciesError" v-on:click="retry('loadCurrencies')">
|
{{$t("Pay with Changelly")}}
|
||||||
{{$t("ConversionTab_LoadCurrencies_Error")}}
|
</button>
|
||||||
</button>
|
}
|
||||||
<div v-show="isLoading" class="general__spinner">
|
|
||||||
<partial name="Checkout-Spinner"/>
|
<iframe
|
||||||
|
v-if="showInlineIFrame"
|
||||||
|
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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</coinswitch>
|
||||||
</changelly>
|
}
|
||||||
}
|
|
||||||
</center>
|
@if (Model.ChangellyEnabled)
|
||||||
</nav>
|
{
|
||||||
</div>
|
<changelly inline-template
|
||||||
}
|
v-if="!srvModel.coinSwitchEnabled || selectedThirdPartyProcessor === 'changelly'"
|
||||||
|
:merchant-id="srvModel.changellyMerchantId"
|
||||||
|
:store-id="srvModel.storeId"
|
||||||
|
:to-currency="srvModel.paymentMethodId"
|
||||||
|
:to-currency-due="srvModel.changellyAmountDue"
|
||||||
|
:to-currency-address="srvModel.btcAddress">
|
||||||
|
<div class="changelly-component">
|
||||||
|
<div class="changelly-component-dropdown-holder" v-show="prettyDropdownInstance">
|
||||||
|
<select
|
||||||
|
v-model="selectedFromCurrency"
|
||||||
|
:disabled="isLoading"
|
||||||
|
v-on:change="onCurrencyChange($event)"
|
||||||
|
ref="changellyCurrenciesDropdown">
|
||||||
|
<option value="">{{$t("ConversionTab_CurrencyList_Select_Option")}}</option>
|
||||||
|
<option v-for="currency of currencies"
|
||||||
|
:data-prefix="'<img src=\''+currency.image+'\'/>'"
|
||||||
|
:value="currency.name">
|
||||||
|
{{currency.fullName}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<a v-on:click="openDialog($event)" :href="url" class="action-button" v-show="url">
|
||||||
|
{{$t("Pay with Changelly")}}
|
||||||
|
</a>
|
||||||
|
@if (Model.CoinSwitchEnabled)
|
||||||
|
{
|
||||||
|
<button v-on:click="$parent.selectedThirdPartyProcessor = 'coinswitch'" class="btn-link mt-2">
|
||||||
|
{{$t("Pay with CoinSwitch")}}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<button class="retry-button" v-if="calculateError" v-on:click="retry('calculateAmount')">
|
||||||
|
{{$t("ConversionTab_CalculateAmount_Error")}}
|
||||||
|
</button>
|
||||||
|
<button class="retry-button" v-if="currenciesError" v-on:click="retry('loadCurrencies')">
|
||||||
|
{{$t("ConversionTab_LoadCurrencies_Error")}}
|
||||||
|
</button>
|
||||||
|
<div v-show="isLoading" class="general__spinner">
|
||||||
|
<partial name="Checkout-Spinner"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</changelly>
|
||||||
|
}
|
||||||
|
</center>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="bp-view" id="paid">
|
<div class="bp-view" id="paid">
|
||||||
<div class="status-block">
|
<div class="status-block">
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
@addTagHelper *, BundlerMinifier.TagHelpers
|
@addTagHelper *, BundlerMinifier.TagHelpers
|
||||||
@inject BTCPayServer.Services.LanguageService langService
|
@inject BTCPayServer.Services.LanguageService langService
|
||||||
@using Newtonsoft.Json
|
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
|
||||||
@using Newtonsoft.Json.Linq
|
|
||||||
@model PaymentModel
|
@model PaymentModel
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
|
@ -152,7 +151,7 @@
|
||||||
return availableLanguages.indexOf(languageCode) >= 0;
|
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
|
// TODO: Move all logic from core.js to Vue controller
|
||||||
Vue.config.ignoredElements = [
|
Vue.config.ignoredElements = [
|
||||||
|
@ -161,7 +160,6 @@
|
||||||
// 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',
|
||||||
|
@ -184,9 +182,18 @@
|
||||||
return this.srvModel.coinSwitchAmountMarkupPercentage
|
return this.srvModel.coinSwitchAmountMarkupPercentage
|
||||||
? this.srvModel.btcDue * (1 + (this.srvModel.coinSwitchAmountMarkupPercentage / 100))
|
? this.srvModel.btcDue * (1 + (this.srvModel.coinSwitchAmountMarkupPercentage / 100))
|
||||||
: this.srvModel.btcDue;
|
: 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>
|
</script>
|
||||||
|
|
||||||
|
@foreach (var paymentMethodHandler in PaymentMethodHandlerDictionary.Select(handler => handler.GetCheckoutUISettings()).Where(settings => settings != null))
|
||||||
|
{
|
||||||
|
<partial name="@paymentMethodHandler.ExtensionPartial" model="@Model"/>
|
||||||
|
}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -16,16 +16,22 @@
|
||||||
<h1>Pay with @Model.StoreName</h1>
|
<h1>Pay with @Model.StoreName</h1>
|
||||||
@if (Model.Status == "new")
|
@if (Model.Status == "new")
|
||||||
{
|
{
|
||||||
<div>
|
if (!string.IsNullOrEmpty(Model.UISettings?.NoScriptPartialName))
|
||||||
<p>To complete payment, please send <b>@Model.BtcDue @Model.CryptoCode</b> to <b style="word-break: break-word;">@Model.BtcAddress</b></p>
|
{
|
||||||
<p>Time remaining: @Model.TimeLeft</p>
|
<partial model="@Model" name="@Model.UISettings.NoScriptPartialName"/>
|
||||||
<p><a href="@Model.InvoiceBitcoinUrl" style="word-break: break-word;">@Model.InvoiceBitcoinUrl</a></p>
|
}
|
||||||
@if (Model.IsLightning)
|
else
|
||||||
{
|
{
|
||||||
<p>Peer Info: <b>@Model.PeerInfo</b></p>
|
<div>
|
||||||
}
|
<p>To complete payment, please send <b>@Model.BtcDue @Model.CryptoCode</b> to <b style="word-break: break-word;">@Model.BtcAddress</b></p>
|
||||||
</div>
|
<p>Time remaining: @Model.TimeLeft</p>
|
||||||
|
<p><a href="@Model.InvoiceBitcoinUrl" style="word-break: break-word;">@Model.InvoiceBitcoinUrl</a></p>
|
||||||
|
@if (Model.IsLightning)
|
||||||
|
{
|
||||||
|
<p>Peer Info: <b>@Model.PeerInfo</b></p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@if (Model.AvailableCryptos.Count > 1)
|
@if (Model.AvailableCryptos.Count > 1)
|
||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,6 +1,29 @@
|
||||||
// TODO: Refactor... switch from jQuery to Vue.js
|
// TODO: Refactor... switch from jQuery to Vue.js
|
||||||
// public methods
|
// public methods
|
||||||
function resetTabsSlider() {
|
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-copy");
|
||||||
$("#tabsSlider").removeClass("slide-altcoins");
|
$("#tabsSlider").removeClass("slide-altcoins");
|
||||||
|
|
||||||
|
@ -27,6 +50,17 @@ function changeCurrency(currency) {
|
||||||
checkoutCtrl.scanDisplayQr = "";
|
checkoutCtrl.scanDisplayQr = "";
|
||||||
srvModel.paymentMethodId = currency;
|
srvModel.paymentMethodId = currency;
|
||||||
fetchStatus();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -162,6 +196,14 @@ function onlyExpandLineItems() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function activateTab(senderName) {
|
||||||
|
$(senderName + "-tab").addClass("active");
|
||||||
|
|
||||||
|
$(senderName).show();
|
||||||
|
$(senderName).addClass("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
// initialize
|
// initialize
|
||||||
|
@ -192,7 +234,7 @@ $(document).ready(function () {
|
||||||
jQuery("invoice").fadeIn(300);
|
jQuery("invoice").fadeIn(300);
|
||||||
|
|
||||||
// eof initialize
|
// eof initialize
|
||||||
|
|
||||||
// Expand Line-Items
|
// Expand Line-Items
|
||||||
$(".buyerTotalLine").click(function () {
|
$(".buyerTotalLine").click(function () {
|
||||||
toggleLineItems();
|
toggleLineItems();
|
||||||
|
@ -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 =============== */
|
/* =============== 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
|
// Payment received
|
||||||
// Should connect using webhook ?
|
// 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);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue