mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-24 06:47:50 +01:00
546 lines
26 KiB
Text
546 lines
26 KiB
Text
|
@inject LanguageService LangService
|
||
|
@inject BTCPayServerEnvironment Env
|
||
|
@inject IFileService FileService
|
||
|
@inject ThemeSettings Theme
|
||
|
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
|
||
|
@using BTCPayServer.Services
|
||
|
@using BTCPayServer.Abstractions.Contracts
|
||
|
@using BTCPayServer.Abstractions.TagHelpers
|
||
|
@using BTCPayServer.Components.ThemeSwitch
|
||
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||
|
@model PaymentModel
|
||
|
@{
|
||
|
Layout = null;
|
||
|
ViewData["Title"] = Model.HtmlTitle;
|
||
|
|
||
|
var paymentMethodCount = Model.AvailableCryptos.Count;
|
||
|
var logoUrl = !string.IsNullOrEmpty(Model.LogoFileId)
|
||
|
? await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.LogoFileId)
|
||
|
: Model.CustomLogoLink;
|
||
|
}
|
||
|
@functions {
|
||
|
private string PaymentMethodName(PaymentModel.AvailableCrypto pm)
|
||
|
{
|
||
|
return Model.AltcoinsBuild
|
||
|
? $"{pm.PaymentMethodName} {pm.CryptoCode}"
|
||
|
: pm.PaymentMethodName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", "");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
<!DOCTYPE html>
|
||
|
<html lang="@Model.DefaultLang">
|
||
|
<head>
|
||
|
<partial name="LayoutHead"/>
|
||
|
<meta name="robots" content="noindex,nofollow">
|
||
|
<link href="~/checkout-v2/checkout.css" asp-append-version="true" rel="stylesheet" />
|
||
|
@if (!string.IsNullOrEmpty(Model.CustomCSSLink))
|
||
|
{
|
||
|
<link href="@Model.CustomCSSLink" rel="stylesheet"/>
|
||
|
}
|
||
|
@if (Model.IsModal)
|
||
|
{
|
||
|
<style>
|
||
|
body { background: rgba(var(--btcpay-black-rgb), 0.85); }
|
||
|
</style>
|
||
|
}
|
||
|
@if (!string.IsNullOrEmpty(Model.BrandColor))
|
||
|
{
|
||
|
<style>
|
||
|
:root {
|
||
|
--btcpay-primary: @Model.BrandColor;
|
||
|
--btcpay-primary-bg-hover: @Model.BrandColor;
|
||
|
--btcpay-primary-bg-active: @Model.BrandColor;
|
||
|
--btcpay-primary-shadow: @Model.BrandColor;
|
||
|
--btcpay-body-link-accent: @Model.BrandColor;
|
||
|
}
|
||
|
</style>
|
||
|
}
|
||
|
</head>
|
||
|
<body>
|
||
|
<div id="Checkout" class="wrap" v-cloak>
|
||
|
<header>
|
||
|
@if (!string.IsNullOrEmpty(logoUrl))
|
||
|
{
|
||
|
<img src="@logoUrl" alt="@Model.StoreName" class="logo @(!string.IsNullOrEmpty(Model.LogoFileId) ? "logo--square" : "")"/>
|
||
|
}
|
||
|
<h1 class="h5 mb-0">@Model.StoreName</h1>
|
||
|
</header>
|
||
|
<main>
|
||
|
<nav v-if="hasNav">
|
||
|
<button type="button" v-if="showBackButton" id="back" v-on:click="back">
|
||
|
<vc:icon symbol="back"/>
|
||
|
</button>
|
||
|
<button type="button" v-if="isModal" id="close" v-on:click="close">
|
||
|
<vc:icon symbol="close"/>
|
||
|
</button>
|
||
|
</nav>
|
||
|
<section id="result" v-if="isPaid || isUnpayable">
|
||
|
<div id="paid" v-if="isPaid">
|
||
|
<div class="top">
|
||
|
<span class="text-success">
|
||
|
<vc:icon symbol="payment-complete"/>
|
||
|
</span>
|
||
|
<h4>{{$t("Invoice paid")}}</h4>
|
||
|
<dl>
|
||
|
<div>
|
||
|
<dt>{{$t("Invoice ID")}}</dt>
|
||
|
<dd>{{srvModel.invoiceId}}</dd>
|
||
|
</div>
|
||
|
<div v-if="srvModel.orderId">
|
||
|
<dt>{{$t("Order ID")}}</dt>
|
||
|
<dd>{{srvModel.orderId}}</dd>
|
||
|
</div>
|
||
|
</dl>
|
||
|
<dl>
|
||
|
<div>
|
||
|
<dt>{{$t("Order Amount")}}</dt>
|
||
|
<dd>{{srvModel.orderAmount}} {{ srvModel.cryptoCode }}</dd>
|
||
|
</div>
|
||
|
<div v-if="srvModel.orderAmountFiat">
|
||
|
<dt>{{$t("Order Amount")}}</dt>
|
||
|
<dd>{{srvModel.orderAmountFiat}}</dd>
|
||
|
</div>
|
||
|
<div v-if="srvModel.networkFee">
|
||
|
<dt>{{$t("Network Cost")}}</dt>
|
||
|
<dd v-if="srvModel.isMultiCurrency">{{ srvModel.networkFee }} {{ srvModel.cryptoCode }}</dd>
|
||
|
<dd v-else-if="srvModel.txCountForFee > 0">{{$t("txCount", {count: srvModel.txCount})}} x {{ srvModel.networkFee }} {{ srvModel.cryptoCode }}</dd>
|
||
|
</div>
|
||
|
<div>
|
||
|
<dt>{{$t("Amount Paid")}}</dt>
|
||
|
<dd>{{srvModel.btcPaid }} {{ srvModel.cryptoCode }}</dd>
|
||
|
</div>
|
||
|
</dl>
|
||
|
</div>
|
||
|
<div class="buttons">
|
||
|
<a class="btn btn-primary" :href="srvModel.receiptLink" v-if="srvModel.receiptLink" :target="isModal ? '_blank' : '_top'">{{$t('View receipt')}}</a>
|
||
|
<a class="btn btn-secondary" :href="srvModel.merchantRefLink" v-if="srvModel.merchantRefLink">{{$t('Return to StoreName', srvModel)}}</a>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div id="expired" v-if="isUnpayable">
|
||
|
<div class="top">
|
||
|
<span class="text-muted">
|
||
|
<vc:icon symbol="invoice-expired"/>
|
||
|
</span>
|
||
|
<h4>{{$t("Invoice expired")}}</h4>
|
||
|
<dl>
|
||
|
<div>
|
||
|
<dt>{{$t("Invoice ID")}}</dt>
|
||
|
<dd>{{srvModel.invoiceId}}</dd>
|
||
|
</div>
|
||
|
<div v-if="srvModel.orderId">
|
||
|
<dt>{{$t("Order ID")}}</dt>
|
||
|
<dd>{{srvModel.orderId}}</dd>
|
||
|
</div>
|
||
|
</dl>
|
||
|
<p v-html="$t('InvoiceExpired_Body_1', {storeName: srvModel.storeName, maxTimeMinutes: @Model.MaxTimeMinutes})"></p>
|
||
|
<p>{{$t("InvoiceExpired_Body_2")}}</p>
|
||
|
<p>{{$t("InvoiceExpired_Body_3")}}</p>
|
||
|
</div>
|
||
|
<div class="buttons">
|
||
|
<a class="btn btn-primary" :href="srvModel.merchantRefLink" v-if="srvModel.merchantRefLink">{{$t('Return to StoreName', srvModel)}}</a>
|
||
|
</div>
|
||
|
</div>
|
||
|
</section>
|
||
|
<section id="form" v-else-if="step === 'form'">
|
||
|
<form method="post" asp-action="UpdateForm" asp-route-invoiceId="@Model.InvoiceId" v-on:submit.prevent="onFormSubmit">
|
||
|
<div class="top">
|
||
|
<h6>{{$t("Please fill out the following")}}</h6>
|
||
|
<div class="timer" v-if="expiringSoon">
|
||
|
{{$t("Invoice will expire in")}} {{timerText}}
|
||
|
<span class="spinner-border spinner-border-sm ms-2" role="status">
|
||
|
<span class="visually-hidden"></span>
|
||
|
</span>
|
||
|
</div>
|
||
|
<template v-if="srvModel.checkoutFormId && srvModel.checkoutFormId !== 'None'">
|
||
|
<p class="my-5 text-center">TODO: Forms integration -> {{srvModel.checkoutFormId}}</p>
|
||
|
</template>
|
||
|
<template v-else-if="srvModel.requiresRefundEmail">
|
||
|
<p>{{$t("Contact_Body")}}</p>
|
||
|
<div class="form-group">
|
||
|
<label class="form-label" for="Email">{{$t("Contact and Refund Email")}}</label>
|
||
|
<input class="form-control" id="Email" name="Email">
|
||
|
<span class="text-danger" hidden>{{$t("Please enter a valid email address")}}</span>
|
||
|
</div>
|
||
|
</template>
|
||
|
</div>
|
||
|
<div class="buttons">
|
||
|
<button type="submit" class="btn btn-primary" :disabled="formSubmitPending" :class="{ 'loading': formSubmitPending }">
|
||
|
{{$t("Continue")}}
|
||
|
<span class="spinner-border spinner-border-sm ms-1" role="status" v-if="formSubmitPending">
|
||
|
<span class="visually-hidden"></span>
|
||
|
</span>
|
||
|
</button>
|
||
|
</div>
|
||
|
</form>
|
||
|
</section>
|
||
|
<section id="payment" v-else>
|
||
|
<h6 class="text-center mb-3 fw-semibold" v-if="srvModel.itemDesc" v-text="srvModel.itemDesc">@Model.ItemDesc</h6>
|
||
|
@if (Model.IsUnsetTopUp)
|
||
|
{
|
||
|
<h2 class="text-center mb-3">{{$t("Any amount")}}</h2>
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
<h2 class="text-center" v-text="`${srvModel.btcDue} ${srvModel.cryptoCode}`" :data-clipboard="srvModel.btcDue">@Model.BtcDue @Model.CryptoCode</h2>
|
||
|
<h2 class="text-center" v-else v-text="`${srvModel.btcDue} ${srvModel.cryptoCode}`" :data-clipboard="srvModel.btcDue">@Model.BtcDue @Model.CryptoCode</h2>
|
||
|
<div class="text-muted text-center fw-semibold" v-if="srvModel.orderAmountFiat" v-text="srvModel.orderAmountFiat">@Model.OrderAmountFiat</div>
|
||
|
<div class="timer" v-if="expiringSoon">
|
||
|
{{$t("Invoice will expire in")}} {{timerText}}
|
||
|
<span class="spinner-border spinner-border-sm ms-2 text-muted" role="status">
|
||
|
<span class="visually-hidden"></span>
|
||
|
</span>
|
||
|
</div>
|
||
|
<div class="mt-3 mb-1 text-center" v-if="showPaymentDueInfo">
|
||
|
<span class="text-info"><vc:icon symbol="info"/></span>
|
||
|
<small>{{$t("NotPaid_ExtraTransaction")}}</small>
|
||
|
</div>
|
||
|
<button class="d-flex align-items-center btn btn-link" type="button" id="PaymentDetailsButton" :aria-expanded="displayPaymentDetails ? 'true' : 'false'" aria-controls="PaymentDetails" v-on:click="displayPaymentDetails = !displayPaymentDetails">
|
||
|
<vc:icon symbol="caret-down"/>
|
||
|
<span class="ms-1 fw-semibold">{{$t("View Details")}}</span>
|
||
|
</button>
|
||
|
<div id="PaymentDetails" class="collapse" v-collapsible="displayPaymentDetails">
|
||
|
<dl>
|
||
|
<div>
|
||
|
<dt>{{$t("Total Price")}}</dt>
|
||
|
<dd :data-clipboard="srvModel.orderAmount">{{srvModel.orderAmount}} {{ srvModel.cryptoCode }}</dd>
|
||
|
</div>
|
||
|
<div v-if="srvModel.orderAmountFiat && srvModel.cryptoCode">
|
||
|
<dt>{{$t("Exchange Rate")}}</dt>
|
||
|
<dd :data-clipboard="srvModel.rate">
|
||
|
<template v-if="srvModel.cryptoCodeSrv === 'Sats'">1 Sat = {{ srvModel.rate }}</template>
|
||
|
<template v-else>1 {{ srvModel.cryptoCodeSrv }} = {{ srvModel.rate }}</template>
|
||
|
</dd>
|
||
|
</div>
|
||
|
<div v-if="srvModel.showRecommendedFee && srvModel.feeRate">
|
||
|
<dt>{{$t("Recommended Fee")}}</dt>
|
||
|
<dd :data-clipboard="srvModel.feeRate">{{$t("Feerate", { feeRate: srvModel.feeRate })}}</dd>
|
||
|
</div>
|
||
|
<div v-if="srvModel.networkFee">
|
||
|
<dt>{{$t("Network Cost")}}</dt>
|
||
|
<dd :data-clipboard="srvModel.networkFee">
|
||
|
<template v-if="srvModel.txCountForFee > 0">{{$t("txCount", {count: srvModel.txCount})}} x</template>
|
||
|
{{ srvModel.networkFee }} {{ srvModel.cryptoCode }}
|
||
|
</dd>
|
||
|
</div>
|
||
|
<div v-if="btcPaid > 0">
|
||
|
<dt>{{$t("Amount Paid")}}</dt>
|
||
|
<dd :data-clipboard="srvModel.btcPaid">{{srvModel.btcPaid }} {{ srvModel.cryptoCode }}</dd>
|
||
|
</div>
|
||
|
<div v-if="btcDue > 0">
|
||
|
<dt>{{$t("Amount Due")}}</dt>
|
||
|
<dd :data-clipboard="srvModel.btcDue">{{srvModel.btcDue}} {{ srvModel.cryptoCode }}</dd>
|
||
|
</div>
|
||
|
</dl>
|
||
|
</div>
|
||
|
}
|
||
|
<div class="my-3">
|
||
|
@if (paymentMethodCount > 1)
|
||
|
{
|
||
|
<h6 class="text-center mb-3">{{$t("Pay with")}}</h6>
|
||
|
<div class="btcpay-pills d-flex flex-wrap align-items-center justify-content-center gap-2 pb-2">
|
||
|
@foreach (var crypto in Model.AvailableCryptos)
|
||
|
{
|
||
|
<a asp-action="Checkout" asp-route-invoiceId="@Model.InvoiceId" asp-route-paymentMethodId="@crypto.PaymentMethodId" class="btcpay-pill m-0@(crypto.PaymentMethodId == Model.PaymentMethodId ? " active" : "")">
|
||
|
@PaymentMethodName(crypto)
|
||
|
</a>
|
||
|
}
|
||
|
</div>
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
<h6 class="text-center mb-3">
|
||
|
{{$t("Pay with")}}
|
||
|
@PaymentMethodName(Model.AvailableCryptos.First())
|
||
|
</h6>
|
||
|
}
|
||
|
</div>
|
||
|
<component v-if="srvModel.uiSettings && srvModel.activated"
|
||
|
:srv-model="srvModel"
|
||
|
:is="srvModel.uiSettings.checkoutBodyVueComponentName"/>
|
||
|
</section>
|
||
|
</main>
|
||
|
@if (Env.CheatMode)
|
||
|
{
|
||
|
<checkout-cheating v-if="step === 'payment'" invoice-id="@Model.InvoiceId" :btc-due="btcDue" :is-paid="isPaid" />
|
||
|
}
|
||
|
<footer>
|
||
|
<select asp-for="DefaultLang" asp-items="@LangService.GetLanguageSelectListItems()" v-on:change="changeLanguage"></select>
|
||
|
|
||
|
<div class="text-muted my-2">
|
||
|
Powered by <a href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">BTCPay Server</a>
|
||
|
</div>
|
||
|
@if (!Theme.CustomTheme)
|
||
|
{
|
||
|
<vc:theme-switch css-class="text-muted ms-n3" responsive="none"/>
|
||
|
}
|
||
|
</footer>
|
||
|
</div>
|
||
|
<noscript>
|
||
|
<div class="p-5 text-center">
|
||
|
<h2>Javascript is currently disabled in your browser.</h2>
|
||
|
<h5>Please enable Javascript and refresh this page for the best experience.</h5>
|
||
|
<p>
|
||
|
Alternatively, click below to continue to our
|
||
|
<a asp-action="CheckoutNoScript" asp-route-invoiceId="@Model.InvoiceId">HTML-only invoice</a>.
|
||
|
</p>
|
||
|
</div>
|
||
|
</noscript>
|
||
|
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
||
|
<script src="~/vendor/vue-qrcode/vue-qrcode.min.js" asp-append-version="true"></script>
|
||
|
<script src="~/vendor/i18next/i18next.js" asp-append-version="true"></script>
|
||
|
<script src="~/vendor/i18next/i18nextXHRBackend.js" asp-append-version="true"></script>
|
||
|
<script src="~/vendor/i18next/vue-i18next.js" asp-append-version="true"></script>
|
||
|
<script src="~/js/copy-to-clipboard.js" asp-append-version="true"></script>
|
||
|
<script src="~/main/utils.js" asp-append-version="true"></script>
|
||
|
<script src="~/checkout-v2/checkout.js" asp-append-version="true"></script>
|
||
|
@if (Env.CheatMode)
|
||
|
{
|
||
|
<partial name="Checkout-Cheating" model="@Model" />
|
||
|
}
|
||
|
<script>
|
||
|
const statusUrl = @Safe.Json(Url.Action("GetStatus", new { invoiceId = Model.InvoiceId }));
|
||
|
const statusWsUrl = @Safe.Json(Url.Action("GetStatusWebSocket", new { invoiceId = Model.InvoiceId }));
|
||
|
const initialSrvModel = @Safe.Json(Model);
|
||
|
const availableLanguages = @Safe.Json(LangService.GetLanguages().Select(language => language.Code));
|
||
|
const defaultLang = @Safe.Json(Model.DefaultLang);
|
||
|
const fallbackLanguage = "en";
|
||
|
const startingLanguage = computeStartingLanguage();
|
||
|
const STATUS_PAID = ['complete', 'confirmed', 'paid'];
|
||
|
const STATUS_UNPAID = ['new', 'paidPartial'];
|
||
|
const STATUS_UNPAYABLE = ['expired', 'invalid'];
|
||
|
const qrOptions = { margin: 1, type: 'svg', color: { dark: '#000', light: '#fff' } };
|
||
|
|
||
|
i18next
|
||
|
.use(window.i18nextXHRBackend)
|
||
|
.init({
|
||
|
backend: {
|
||
|
loadPath: @Safe.Json($"{Model.RootPath}locales/{{{{lng}}}}.json")
|
||
|
},
|
||
|
lng: startingLanguage,
|
||
|
fallbackLng: fallbackLanguage,
|
||
|
nsSeparator: false,
|
||
|
keySeparator: false,
|
||
|
load: 'currentOnly'
|
||
|
});
|
||
|
|
||
|
function computeStartingLanguage() {
|
||
|
if (urlParams.lang && isLanguageAvailable(urlParams.lang)) {
|
||
|
return urlParams.lang;
|
||
|
}
|
||
|
else if (isLanguageAvailable(defaultLang)) {
|
||
|
return defaultLang;
|
||
|
} else {
|
||
|
return fallbackLanguage;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function isLanguageAvailable(languageCode) {
|
||
|
return availableLanguages.indexOf(languageCode) >= 0;
|
||
|
}
|
||
|
|
||
|
const i18n = new VueI18next(i18next);
|
||
|
const eventBus = new Vue();
|
||
|
|
||
|
new Vue({
|
||
|
i18n,
|
||
|
el: '#Checkout',
|
||
|
data () {
|
||
|
const srvModel = initialSrvModel;
|
||
|
let step = 'payment';
|
||
|
if (STATUS_UNPAYABLE.concat(STATUS_PAID).includes(srvModel.status)) {
|
||
|
step = 'result';
|
||
|
} else if (srvModel.requiresRefundEmail || (srvModel.checkoutFormId && srvModel.checkoutFormId !== 'None')) {
|
||
|
step = 'form';
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
srvModel,
|
||
|
step,
|
||
|
displayPaymentDetails: false,
|
||
|
end: new Date(),
|
||
|
expirationPercentage: 0,
|
||
|
timerText: @Safe.Json(Model.TimeLeft),
|
||
|
emailAddressInput: "",
|
||
|
emailAddressInputDirty: false,
|
||
|
emailAddressInputInvalid: false,
|
||
|
formSubmitPending: false,
|
||
|
isModal: srvModel.isModal
|
||
|
}
|
||
|
},
|
||
|
computed: {
|
||
|
expiringSoon () {
|
||
|
return this.isActive && this.expirationPercentage >= 75;
|
||
|
},
|
||
|
showRecommendedFee () {
|
||
|
return this.srvModel.showRecommendedFee && this.srvModel.feeRate !== 0;
|
||
|
},
|
||
|
isUnpayable () {
|
||
|
return STATUS_UNPAYABLE.includes(this.srvModel.status);
|
||
|
},
|
||
|
isPaid () {
|
||
|
return STATUS_PAID.includes(this.srvModel.status);
|
||
|
},
|
||
|
isActive () {
|
||
|
return !this.isUnpayable && !this.isPaid;
|
||
|
},
|
||
|
hasNav () {
|
||
|
return this.isModal || this.showBackButton;
|
||
|
},
|
||
|
hasForm () {
|
||
|
return this.srvModel.requiresRefundEmail || (
|
||
|
this.srvModel.checkoutFormId && this.srvModel.checkoutFormId !== 'None');
|
||
|
},
|
||
|
showBackButton () {
|
||
|
return this.hasForm && this.step === 'payment';
|
||
|
},
|
||
|
showPaymentDueInfo () {
|
||
|
return this.btcPaid > 0 && this.btcDue > 0;
|
||
|
},
|
||
|
btcDue () {
|
||
|
return parseFloat(this.srvModel.btcDue);
|
||
|
},
|
||
|
btcPaid () {
|
||
|
return parseFloat(this.srvModel.btcPaid);
|
||
|
}
|
||
|
},
|
||
|
mounted () {
|
||
|
this.onDataCallback(this.srvModel);
|
||
|
if (this.isActive) {
|
||
|
this.updateProgressTimer();
|
||
|
this.listenIn();
|
||
|
}
|
||
|
window.parent.postMessage('loaded', '*');
|
||
|
},
|
||
|
methods: {
|
||
|
changeLanguage(e) {
|
||
|
const lang = e.target.value;
|
||
|
if (isLanguageAvailable(lang)) {
|
||
|
i18next.changeLanguage(lang);
|
||
|
}
|
||
|
},
|
||
|
back () {
|
||
|
this.step = 'form';
|
||
|
},
|
||
|
close () {
|
||
|
window.parent.postMessage('close', '*');
|
||
|
},
|
||
|
updateProgressTimer () {
|
||
|
const timeLeftS = this.endDate
|
||
|
? Math.floor((this.endDate.getTime() - new Date().getTime())/1000)
|
||
|
: this.srvModel.expirationSeconds;
|
||
|
this.expirationPercentage = 100 - ((timeLeftS / this.srvModel.maxTimeSeconds) * 100);
|
||
|
this.timerText = this.updateTimerText(timeLeftS);
|
||
|
if (this.expirationPercentage < 100 && STATUS_UNPAID.includes(this.srvModel.status)){
|
||
|
setTimeout(this.updateProgressTimer, 500);
|
||
|
}
|
||
|
},
|
||
|
minutesLeft (timer) {
|
||
|
const val = Math.floor(timer / 60);
|
||
|
return val < 10 ? `0${val}` : val;
|
||
|
},
|
||
|
secondsLeft (timer) {
|
||
|
const val = Math.floor(timer % 60);
|
||
|
return val < 10 ? `0${val}` : val;
|
||
|
},
|
||
|
updateTimerText (timer) {
|
||
|
return timer >= 0
|
||
|
? `${this.minutesLeft(timer)}:${this.secondsLeft(timer)}`
|
||
|
: '00:00';
|
||
|
},
|
||
|
listenIn () {
|
||
|
let socket;
|
||
|
const updateFn = this.fetchData;
|
||
|
const supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||
|
if (supportsWebSockets) {
|
||
|
const protocol = window.location.protocol.replace('http', 'ws');
|
||
|
const wsUri = `${protocol}//${window.location.host}${statusWsUrl}`;
|
||
|
try {
|
||
|
socket = new WebSocket(wsUri);
|
||
|
socket.onmessage = e => {
|
||
|
if (e.data === 'ping') return;
|
||
|
updateFn();
|
||
|
};
|
||
|
socket.onerror = e => {
|
||
|
console.error('Error while connecting to websocket for invoice notifications (callback):', e);
|
||
|
};
|
||
|
}
|
||
|
catch (e) {
|
||
|
console.error('Error while connecting to websocket for invoice notifications', e);
|
||
|
}
|
||
|
}
|
||
|
(function watcher() {
|
||
|
setTimeout(() => {
|
||
|
if (socket === null || socket.readyState !== 1) {
|
||
|
updateFn();
|
||
|
}
|
||
|
watcher();
|
||
|
}, 2000);
|
||
|
})();
|
||
|
},
|
||
|
async onFormSubmit (e) {
|
||
|
const form = e.target;
|
||
|
const url = form.getAttribute('action');
|
||
|
const method = form.getAttribute('method');
|
||
|
const body = new FormData(form);
|
||
|
const headers = { 'Content-Type': 'application/json' };
|
||
|
|
||
|
this.formSubmitPending = true;
|
||
|
const response = await fetch(url, { method, body, headers });
|
||
|
this.formSubmitPending = false;
|
||
|
if (response.ok) {
|
||
|
// TODO
|
||
|
this.step = 'payment';
|
||
|
}
|
||
|
},
|
||
|
async fetchData () {
|
||
|
const url = `${statusUrl}&paymentMethodId=${this.srvModel.paymentMethodId}`;
|
||
|
const response = await fetch(url);
|
||
|
if (response.ok) {
|
||
|
const data = await response.json();
|
||
|
this.onDataCallback(data);
|
||
|
}
|
||
|
},
|
||
|
onDataCallback (jsonData) {
|
||
|
if (this.srvModel.status !== jsonData.status) {
|
||
|
const { invoiceId } = this.srvModel;
|
||
|
const { status } = jsonData;
|
||
|
window.parent.postMessage({ invoiceId, status }, '*');
|
||
|
}
|
||
|
|
||
|
// displaying satoshis for lightning payments
|
||
|
jsonData.cryptoCodeSrv = jsonData.cryptoCode;
|
||
|
|
||
|
const newEnd = new Date();
|
||
|
newEnd.setSeconds(newEnd.getSeconds() + jsonData.expirationSeconds);
|
||
|
this.endDate = newEnd;
|
||
|
|
||
|
// updating ui
|
||
|
this.srvModel = jsonData;
|
||
|
eventBus.$emit('data-fetched', this.srvModel);
|
||
|
|
||
|
if (this.isPaid && jsonData.redirectAutomatically && jsonData.merchantRefLink) {
|
||
|
setTimeout(function () {
|
||
|
if (this.isModal && window.top.location == jsonData.merchantRefLink){
|
||
|
this.close();
|
||
|
} else {
|
||
|
window.top.location = jsonData.merchantRefLink;
|
||
|
}
|
||
|
}.bind(this), 2000);
|
||
|
} else if (!this.isActive) {
|
||
|
this.step = 'result';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
</script>
|
||
|
@foreach (var paymentMethodHandler in PaymentMethodHandlerDictionary
|
||
|
.Select(handler => handler.GetCheckoutUISettings())
|
||
|
.Where(settings => settings != null)
|
||
|
.DistinctBy(pm => pm.ExtensionPartial))
|
||
|
{
|
||
|
<partial name="@paymentMethodHandler.ExtensionPartial-v2" model="@Model"/>
|
||
|
}
|
||
|
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-end", model = Model })
|
||
|
</body>
|
||
|
</html>
|