mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Checkout v2: Improve expired paid partial state (#4827)
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
This commit is contained in:
@@ -16,6 +16,8 @@ namespace BTCPayServer.Client.Models
|
|||||||
|
|
||||||
public string Website { get; set; }
|
public string Website { get; set; }
|
||||||
|
|
||||||
|
public string SupportUrl { get; set; }
|
||||||
|
|
||||||
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]
|
||||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public TimeSpan InvoiceExpiration { get; set; } = TimeSpan.FromMinutes(15);
|
public TimeSpan InvoiceExpiration { get; set; } = TimeSpan.FromMinutes(15);
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client.Models;
|
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Tests.Logging;
|
|
||||||
using BTCPayServer.Views.Stores;
|
using BTCPayServer.Views.Stores;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Support.Extensions;
|
|
||||||
using OpenQA.Selenium.Support.UI;
|
using OpenQA.Selenium.Support.UI;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
@@ -40,8 +36,10 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
// Configure store url
|
// Configure store url
|
||||||
var storeUrl = "https://satoshisteaks.com/";
|
var storeUrl = "https://satoshisteaks.com/";
|
||||||
|
var supportUrl = "https://support.satoshisteaks.com/{InvoiceId}/";
|
||||||
s.GoToStore();
|
s.GoToStore();
|
||||||
s.Driver.FindElement(By.Id("StoreWebsite")).SendKeys(storeUrl);
|
s.Driver.FindElement(By.Id("StoreWebsite")).SendKeys(storeUrl);
|
||||||
|
s.Driver.FindElement(By.Id("StoreSupportUrl")).SendKeys(supportUrl);
|
||||||
s.Driver.FindElement(By.Id("Save")).Click();
|
s.Driver.FindElement(By.Id("Save")).Click();
|
||||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||||
|
|
||||||
@@ -140,8 +138,47 @@ namespace BTCPayServer.Tests
|
|||||||
var expiredSection = s.Driver.FindElement(By.Id("unpaid"));
|
var expiredSection = s.Driver.FindElement(By.Id("unpaid"));
|
||||||
Assert.True(expiredSection.Displayed);
|
Assert.True(expiredSection.Displayed);
|
||||||
Assert.Contains("Invoice Expired", expiredSection.Text);
|
Assert.Contains("Invoice Expired", expiredSection.Text);
|
||||||
|
Assert.Contains("resubmit a payment", expiredSection.Text);
|
||||||
|
Assert.DoesNotContain("This invoice expired with partial payment", expiredSection.Text);
|
||||||
|
|
||||||
});
|
});
|
||||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("receipt-btn")));
|
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
|
||||||
|
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||||
|
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||||
|
|
||||||
|
// Expire paid partial
|
||||||
|
s.GoToHome();
|
||||||
|
invoiceId = s.CreateInvoice(2100, "EUR");
|
||||||
|
s.GoToInvoiceCheckout(invoiceId);
|
||||||
|
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||||
|
|
||||||
|
await Task.Delay(200);
|
||||||
|
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||||
|
var amountFraction = "0.00001";
|
||||||
|
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
||||||
|
Money.Parse(amountFraction));
|
||||||
|
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||||
|
|
||||||
|
expirySeconds = s.Driver.FindElement(By.Id("ExpirySeconds"));
|
||||||
|
expirySeconds.Clear();
|
||||||
|
expirySeconds.SendKeys("3");
|
||||||
|
s.Driver.FindElement(By.Id("Expire")).Click();
|
||||||
|
|
||||||
|
paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
|
||||||
|
Assert.Contains("The invoice hasn't been paid in full.", paymentInfo.Text);
|
||||||
|
Assert.Contains("Please send", paymentInfo.Text);
|
||||||
|
TestUtils.Eventually(() =>
|
||||||
|
{
|
||||||
|
var expiredSection = s.Driver.FindElement(By.Id("unpaid"));
|
||||||
|
Assert.True(expiredSection.Displayed);
|
||||||
|
Assert.Contains("Invoice Expired", expiredSection.Text);
|
||||||
|
Assert.Contains("This invoice expired with partial payment", expiredSection.Text);
|
||||||
|
Assert.DoesNotContain("resubmit a payment", expiredSection.Text);
|
||||||
|
});
|
||||||
|
var contactLink = s.Driver.FindElement(By.Id("ContactLink"));
|
||||||
|
Assert.Equal("Contact us", contactLink.Text);
|
||||||
|
Assert.Matches(supportUrl.Replace("{InvoiceId}", invoiceId), contactLink.GetAttribute("href"));
|
||||||
|
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
|
||||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||||
|
|
||||||
// Test payment
|
// Test payment
|
||||||
@@ -166,7 +203,7 @@ namespace BTCPayServer.Tests
|
|||||||
// Pay partial amount
|
// Pay partial amount
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||||
var amountFraction = "0.00001";
|
amountFraction = "0.00001";
|
||||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
||||||
Money.Parse(amountFraction));
|
Money.Parse(amountFraction));
|
||||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||||
@@ -210,7 +247,8 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Contains("Invoice Paid", settledSection.Text);
|
Assert.Contains("Invoice Paid", settledSection.Text);
|
||||||
});
|
});
|
||||||
s.Driver.FindElement(By.Id("confetti"));
|
s.Driver.FindElement(By.Id("confetti"));
|
||||||
s.Driver.FindElement(By.Id("receipt-btn"));
|
s.Driver.FindElement(By.Id("ReceiptLink"));
|
||||||
|
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
|
||||||
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
Assert.Equal(storeUrl, s.Driver.FindElement(By.Id("StoreLink")).GetAttribute("href"));
|
||||||
|
|
||||||
// BIP21
|
// BIP21
|
||||||
@@ -358,6 +396,7 @@ namespace BTCPayServer.Tests
|
|||||||
s.GoToHome();
|
s.GoToHome();
|
||||||
s.GoToLightningSettings();
|
s.GoToLightningSettings();
|
||||||
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), false);
|
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), false);
|
||||||
|
s.Driver.ScrollTo(By.Id("save"));
|
||||||
s.Driver.FindElement(By.Id("save")).Click();
|
s.Driver.FindElement(By.Id("save")).Click();
|
||||||
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||||
|
|
||||||
|
|||||||
@@ -600,7 +600,7 @@ namespace BTCPayServer.Tests
|
|||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
{
|
{
|
||||||
s.Driver.Navigate().Refresh();
|
s.Driver.Navigate().Refresh();
|
||||||
s.Driver.FindElement(By.Id("receipt-btn")).Click();
|
s.Driver.FindElement(By.Id("ReceiptLink")).Click();
|
||||||
});
|
});
|
||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
{
|
{
|
||||||
@@ -612,7 +612,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
await s.Server.PayTester.InvoiceRepository.MarkInvoiceStatus(i, InvoiceStatus.Settled);
|
await s.Server.PayTester.InvoiceRepository.MarkInvoiceStatus(i, InvoiceStatus.Settled);
|
||||||
|
|
||||||
TestUtils.Eventually(() => s.Driver.FindElement(By.Id("receipt-btn")).Click());
|
TestUtils.Eventually(() => s.Driver.FindElement(By.Id("ReceiptLink")).Click());
|
||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
{
|
{
|
||||||
s.Driver.Navigate().Refresh();
|
s.Driver.Navigate().Refresh();
|
||||||
|
|||||||
@@ -115,11 +115,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
internal static Client.Models.StoreData FromModel(Data.StoreData data)
|
internal static Client.Models.StoreData FromModel(Data.StoreData data)
|
||||||
{
|
{
|
||||||
var storeBlob = data.GetStoreBlob();
|
var storeBlob = data.GetStoreBlob();
|
||||||
return new Client.Models.StoreData()
|
return new Client.Models.StoreData
|
||||||
{
|
{
|
||||||
Id = data.Id,
|
Id = data.Id,
|
||||||
Name = data.StoreName,
|
Name = data.StoreName,
|
||||||
Website = data.StoreWebsite,
|
Website = data.StoreWebsite,
|
||||||
|
SupportUrl = storeBlob.StoreSupportUrl,
|
||||||
SpeedPolicy = data.SpeedPolicy,
|
SpeedPolicy = data.SpeedPolicy,
|
||||||
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToStringNormalized(),
|
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToStringNormalized(),
|
||||||
//blob
|
//blob
|
||||||
@@ -186,6 +187,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
blob.ShowRecommendedFee = restModel.ShowRecommendedFee;
|
blob.ShowRecommendedFee = restModel.ShowRecommendedFee;
|
||||||
blob.RecommendedFeeBlockTarget = restModel.RecommendedFeeBlockTarget;
|
blob.RecommendedFeeBlockTarget = restModel.RecommendedFeeBlockTarget;
|
||||||
blob.DefaultLang = restModel.DefaultLang;
|
blob.DefaultLang = restModel.DefaultLang;
|
||||||
|
blob.StoreSupportUrl = restModel.SupportUrl;
|
||||||
blob.MonitoringExpiration = restModel.MonitoringExpiration;
|
blob.MonitoringExpiration = restModel.MonitoringExpiration;
|
||||||
blob.InvoiceExpiration = restModel.InvoiceExpiration;
|
blob.InvoiceExpiration = restModel.InvoiceExpiration;
|
||||||
blob.DisplayExpirationTimer = restModel.DisplayExpirationTimer;
|
blob.DisplayExpirationTimer = restModel.DisplayExpirationTimer;
|
||||||
|
|||||||
@@ -854,16 +854,23 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
var isAltcoinsBuild = false;
|
var isAltcoinsBuild = false;
|
||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
isAltcoinsBuild = true;
|
isAltcoinsBuild = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
var orderId = invoice.Metadata.OrderId;
|
||||||
|
var supportUrl = !string.IsNullOrEmpty(storeBlob.StoreSupportUrl)
|
||||||
|
? storeBlob.StoreSupportUrl
|
||||||
|
.Replace("{OrderId}", string.IsNullOrEmpty(orderId) ? string.Empty : Uri.EscapeDataString(orderId))
|
||||||
|
.Replace("{InvoiceId}", Uri.EscapeDataString(invoice.Id))
|
||||||
|
: null;
|
||||||
|
|
||||||
var model = new PaymentModel
|
var model = new PaymentModel
|
||||||
{
|
{
|
||||||
Activated = paymentMethodDetails.Activated,
|
Activated = paymentMethodDetails.Activated,
|
||||||
CryptoCode = network.CryptoCode,
|
CryptoCode = network.CryptoCode,
|
||||||
RootPath = Request.PathBase.Value.WithTrailingSlash(),
|
RootPath = Request.PathBase.Value.WithTrailingSlash(),
|
||||||
OrderId = invoice.Metadata.OrderId,
|
OrderId = orderId,
|
||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoiceId,
|
||||||
DefaultLang = lang ?? invoice.DefaultLanguage ?? storeBlob.DefaultLang ?? "en",
|
DefaultLang = lang ?? invoice.DefaultLanguage ?? storeBlob.DefaultLang ?? "en",
|
||||||
ShowPayInWalletButton = storeBlob.ShowPayInWalletButton,
|
ShowPayInWalletButton = storeBlob.ShowPayInWalletButton,
|
||||||
ShowStoreHeader = storeBlob.ShowStoreHeader,
|
ShowStoreHeader = storeBlob.ShowStoreHeader,
|
||||||
@@ -895,6 +902,7 @@ namespace BTCPayServer.Controllers
|
|||||||
ReceiptLink = receiptUrl,
|
ReceiptLink = receiptUrl,
|
||||||
RedirectAutomatically = invoice.RedirectAutomatically,
|
RedirectAutomatically = invoice.RedirectAutomatically,
|
||||||
StoreName = store.StoreName,
|
StoreName = store.StoreName,
|
||||||
|
StoreSupportUrl = supportUrl,
|
||||||
TxCount = accounting.TxRequired,
|
TxCount = accounting.TxRequired,
|
||||||
TxCountForFee = storeBlob.NetworkFeeMode switch
|
TxCountForFee = storeBlob.NetworkFeeMode switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ namespace BTCPayServer.Controllers
|
|||||||
entity.Type = InvoiceType.TopUp;
|
entity.Type = InvoiceType.TopUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entity.StoreSupportUrl = storeBlob.StoreSupportUrl;
|
||||||
entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite;
|
entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite;
|
||||||
entity.RedirectAutomatically =
|
entity.RedirectAutomatically =
|
||||||
invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically);
|
invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically);
|
||||||
|
|||||||
@@ -611,6 +611,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Id = store.Id,
|
Id = store.Id,
|
||||||
StoreName = store.StoreName,
|
StoreName = store.StoreName,
|
||||||
StoreWebsite = store.StoreWebsite,
|
StoreWebsite = store.StoreWebsite,
|
||||||
|
StoreSupportUrl = storeBlob.StoreSupportUrl,
|
||||||
LogoFileId = storeBlob.LogoFileId,
|
LogoFileId = storeBlob.LogoFileId,
|
||||||
CssFileId = storeBlob.CssFileId,
|
CssFileId = storeBlob.CssFileId,
|
||||||
BrandColor = storeBlob.BrandColor,
|
BrandColor = storeBlob.BrandColor,
|
||||||
@@ -646,6 +647,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var blob = CurrentStore.GetStoreBlob();
|
var blob = CurrentStore.GetStoreBlob();
|
||||||
|
blob.StoreSupportUrl = model.StoreSupportUrl;
|
||||||
blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice;
|
blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice;
|
||||||
blob.NetworkFeeMode = model.NetworkFeeMode;
|
blob.NetworkFeeMode = model.NetworkFeeMode;
|
||||||
blob.PaymentTolerance = model.PaymentTolerance;
|
blob.PaymentTolerance = model.PaymentTolerance;
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ namespace BTCPayServer.Data
|
|||||||
_DefaultCurrency = _DefaultCurrency.Trim().ToUpperInvariant();
|
_DefaultCurrency = _DefaultCurrency.Trim().ToUpperInvariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string StoreSupportUrl { get; set; }
|
||||||
|
|
||||||
CurrencyPair[] _DefaultCurrencyPairs;
|
CurrencyPair[] _DefaultCurrencyPairs;
|
||||||
[JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))]
|
[JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))]
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
public int TxCount { get; set; }
|
public int TxCount { get; set; }
|
||||||
public int TxCountForFee { get; set; }
|
public int TxCountForFee { get; set; }
|
||||||
public string BtcPaid { get; set; }
|
public string BtcPaid { get; set; }
|
||||||
public string StoreEmail { get; set; }
|
public string StoreSupportUrl { get; set; }
|
||||||
|
|
||||||
public string OrderId { get; set; }
|
public string OrderId { get; set; }
|
||||||
public decimal NetworkFee { get; set; }
|
public decimal NetworkFee { get; set; }
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
[MaxLength(500)]
|
[MaxLength(500)]
|
||||||
public string StoreWebsite { get; set; }
|
public string StoreWebsite { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Support URL")]
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string StoreSupportUrl { get; set; }
|
||||||
|
|
||||||
[Display(Name = "Brand Color")]
|
[Display(Name = "Brand Color")]
|
||||||
public string BrandColor { get; set; }
|
public string BrandColor { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -408,6 +408,8 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
// public bool Refundable { get; set; }
|
// public bool Refundable { get; set; }
|
||||||
public bool? RequiresRefundEmail { get; set; } = null;
|
public bool? RequiresRefundEmail { get; set; } = null;
|
||||||
public string RefundMail { get; set; }
|
public string RefundMail { get; set; }
|
||||||
|
|
||||||
|
public string StoreSupportUrl { get; set; }
|
||||||
[JsonProperty("redirectURL")]
|
[JsonProperty("redirectURL")]
|
||||||
public string RedirectURLTemplate { get; set; }
|
public string RedirectURLTemplate { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a v-if="srvModel.receiptLink" class="btn btn-primary rounded-pill w-100" :href="srvModel.receiptLink" :target="isModal ? '_top' : null" v-t="'view_receipt'" id="receipt-btn"></a>
|
<a v-if="srvModel.receiptLink" class="btn btn-primary rounded-pill w-100" :href="srvModel.receiptLink" :target="isModal ? '_top' : null" v-t="'view_receipt'" id="ReceiptLink"></a>
|
||||||
<a v-if="storeLink" class="btn btn-secondary rounded-pill w-100" :href="storeLink" :target="isModal ? '_top' : null" v-html="$t('return_to_store', { storeName: srvModel.storeName })" id="StoreLink"></a>
|
<a v-if="storeLink" class="btn btn-secondary rounded-pill w-100" :href="storeLink" :target="isModal ? '_top' : null" v-html="$t('return_to_store', { storeName: srvModel.storeName })" id="StoreLink"></a>
|
||||||
<button v-else-if="isModal" class="btn btn-secondary rounded-pill w-100" v-on:click="close" v-t="'Close'"></button>
|
<button v-else-if="isModal" class="btn btn-secondary rounded-pill w-100" v-on:click="close" v-t="'Close'"></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,9 +198,10 @@
|
|||||||
<span class="fw-semibold" v-t="'view_details'"></span>
|
<span class="fw-semibold" v-t="'view_details'"></span>
|
||||||
<vc:icon symbol="caret-down" />
|
<vc:icon symbol="caret-down" />
|
||||||
</button>
|
</button>
|
||||||
<p class="text-center mt-3" v-html="replaceNewlines($t('invoice_expired_body', { storeName: srvModel.storeName, minutes: @Model.MaxTimeMinutes }))"></p>
|
<p class="text-center mt-3" v-html="replaceNewlines($t(isPaidPartial ? 'invoice_paidpartial_body' : 'invoice_expired_body', { storeName: srvModel.storeName, minutes: srvModel.maxTimeMinutes }))"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
<a v-if="isPaidPartial && srvModel.storeSupportUrl" class="btn btn-primary rounded-pill w-100" :href="srvModel.storeSupportUrl" v-t="'contact_us'" id="ContactLink"></a>
|
||||||
<a v-if="storeLink" class="btn btn-primary rounded-pill w-100" :href="storeLink" :target="isModal ? '_top' : null" v-html="$t('return_to_store', { storeName: srvModel.storeName })" id="StoreLink"></a>
|
<a v-if="storeLink" class="btn btn-primary rounded-pill w-100" :href="storeLink" :target="isModal ? '_top' : null" v-html="$t('return_to_store', { storeName: srvModel.storeName })" id="StoreLink"></a>
|
||||||
<button v-else-if="isModal" class="btn btn-primary rounded-pill w-100" v-on:click="close" v-t="'Close'"></button>
|
<button v-else-if="isModal" class="btn btn-primary rounded-pill w-100" v-on:click="close" v-t="'Close'"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,6 +32,14 @@
|
|||||||
<input asp-for="StoreWebsite" class="form-control" />
|
<input asp-for="StoreWebsite" class="form-control" />
|
||||||
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
|
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="StoreSupportUrl" class="form-label"></label>
|
||||||
|
<input asp-for="StoreSupportUrl" class="form-control" />
|
||||||
|
<span asp-validation-for="StoreSupportUrl" class="text-danger"></span>
|
||||||
|
<div class="form-text">
|
||||||
|
For support requests, can contain the placeholders <code>{OrderId}</code> and <code>{InvoiceId}</code>. Can be any valid URI, such as a website, email, and nostr.",
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-5 mb-3">Branding</h3>
|
<h3 class="mt-5 mb-3">Branding</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ function initApp() {
|
|||||||
isActive () {
|
isActive () {
|
||||||
return STATUS_PAYABLE.includes(this.srvModel.status);
|
return STATUS_PAYABLE.includes(this.srvModel.status);
|
||||||
},
|
},
|
||||||
|
isPaidPartial () {
|
||||||
|
return this.btcPaid > 0 && this.btcDue > 0;
|
||||||
|
},
|
||||||
showInfo () {
|
showInfo () {
|
||||||
return this.showTimer || this.showPaymentDueInfo;
|
return this.showTimer || this.showPaymentDueInfo;
|
||||||
},
|
},
|
||||||
@@ -139,7 +142,7 @@ function initApp() {
|
|||||||
return this.isActive && this.remainingSeconds < this.srvModel.displayExpirationTimer;
|
return this.isActive && this.remainingSeconds < this.srvModel.displayExpirationTimer;
|
||||||
},
|
},
|
||||||
showPaymentDueInfo () {
|
showPaymentDueInfo () {
|
||||||
return this.btcPaid > 0 && this.btcDue > 0;
|
return this.isPaidPartial;
|
||||||
},
|
},
|
||||||
showRecommendedFee () {
|
showRecommendedFee () {
|
||||||
return this.isActive && this.srvModel.showRecommendedFee && this.srvModel.feeRate;
|
return this.isActive && this.srvModel.showRecommendedFee && this.srvModel.feeRate;
|
||||||
@@ -320,7 +323,6 @@ function initApp() {
|
|||||||
const { status } = data;
|
const { status } = data;
|
||||||
window.parent.postMessage({ invoiceId, status }, '*');
|
window.parent.postMessage({ invoiceId, status }, '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
const newEnd = new Date();
|
const newEnd = new Date();
|
||||||
newEnd.setSeconds(newEnd.getSeconds() + data.expirationSeconds);
|
newEnd.setSeconds(newEnd.getSeconds() + data.expirationSeconds);
|
||||||
this.endDate = newEnd;
|
this.endDate = newEnd;
|
||||||
|
|||||||
@@ -34,10 +34,12 @@
|
|||||||
"invoice_paid": "Invoice Paid",
|
"invoice_paid": "Invoice Paid",
|
||||||
"invoice_expired": "Invoice Expired",
|
"invoice_expired": "Invoice Expired",
|
||||||
"invoice_expired_body": "An invoice is only valid for {{minutes}} minutes.\n\nReturn to {{storeName}} if you would like to resubmit a payment.",
|
"invoice_expired_body": "An invoice is only valid for {{minutes}} minutes.\n\nReturn to {{storeName}} if you would like to resubmit a payment.",
|
||||||
|
"invoice_paidpartial_body": "An invoice is only valid for {{minutes}} minutes.\n\nThis invoice expired with partial payment. Please contact us, so that we can fulfill your order.",
|
||||||
"view_receipt": "View receipt",
|
"view_receipt": "View receipt",
|
||||||
"return_to_store": "Return to {{storeName}}",
|
"return_to_store": "Return to {{storeName}}",
|
||||||
|
"contact_us": "Contact us",
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"copy_confirm": "Copied",
|
"copy_confirm": "Copied",
|
||||||
"powered_by": "Powered by",
|
"powered_by": "Powered by",
|
||||||
"conversion_body": "This service is provided by 3rd party. Please keep in mind that we have no control over how providers will forward your funds. Invoice will only be marked paid once funds are received on the {{cryptoCode}} blockchain."
|
"conversion_body": "This service is provided by 3rd party. Please keep in mind that we have no control over how providers will forward your funds. Invoice will only be marked paid once funds are received on the {{cryptoCode}} blockchain."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,6 +323,12 @@
|
|||||||
"description": "The absolute url of the store",
|
"description": "The absolute url of the store",
|
||||||
"format": "url"
|
"format": "url"
|
||||||
},
|
},
|
||||||
|
"supportUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "The support URI of the store, can contain the placeholders `{OrderId}` and `{InvoiceId}`. Can be any valid URI, such as a website, email, and nostr.",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
"defaultCurrency": {
|
"defaultCurrency": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The default currency of the store",
|
"description": "The default currency of the store",
|
||||||
|
|||||||
Reference in New Issue
Block a user