mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-10 17:26:05 +01:00
Checkout v2: Improve expired paid partial state (#4827)
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
This commit is contained in:
parent
37f0498def
commit
25fb5c1293
16 changed files with 100 additions and 19 deletions
|
@ -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;
|
||||||
|
|
|
@ -857,13 +857,20 @@ namespace BTCPayServer.Controllers
|
||||||
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;
|
||||||
|
|
|
@ -66,6 +66,8 @@ namespace BTCPayServer.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string StoreSupportUrl { get; set; }
|
||||||
|
|
||||||
CurrencyPair[] _DefaultCurrencyPairs;
|
CurrencyPair[] _DefaultCurrencyPairs;
|
||||||
[JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))]
|
[JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))]
|
||||||
public CurrencyPair[] DefaultCurrencyPairs
|
public CurrencyPair[] DefaultCurrencyPairs
|
||||||
|
|
|
@ -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,8 +34,10 @@
|
||||||
"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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Add table
Reference in a new issue