Compare commits

..

29 Commits

Author SHA1 Message Date
0d91b3286a bump 2018-03-29 13:00:04 +09:00
396432b873 Remove ESSLint errors 2018-03-29 12:54:58 +09:00
15c58434e8 Renaming CreateInvoiceResponse to CLightningInvoice 2018-03-29 12:54:07 +09:00
daad1bdd25 Fix bug of lightning invoice notification spam at startup 2018-03-29 12:36:10 +09:00
c60966c725 Revert "Add temporary log for stufftech debug"
This reverts commit fb57d8c3ce9f4b6e5d1ced035cda4cd385e6c75a.
2018-03-29 12:25:26 +09:00
fb57d8c3ce Add temporary log for stufftech debug 2018-03-29 12:21:20 +09:00
799ce74f65 Add temporary log for stufftech debug 2018-03-29 12:20:06 +09:00
8e38d7ceb4 Revert "Add temporary log to debug stufftech"
This reverts commit a1c22e807146b46e90cf78dd542bd4e3a6f67bf7.
2018-03-29 12:17:03 +09:00
a1c22e8071 Add temporary log to debug stufftech 2018-03-29 12:14:51 +09:00
6d8acf54d6 Revert "Fix SQLite bug: New invoice repeating"
This reverts commit 9eb3aad072c0c28dc937e6317425b2a3a0e1ed94.
2018-03-29 12:10:03 +09:00
a500a89138 Revert "add hack sqlite specific"
This reverts commit c6d44e7a8936fe9ac7bb85f221ed176afd8b8540.
2018-03-29 12:09:57 +09:00
c6d44e7a89 add hack sqlite specific 2018-03-29 12:02:13 +09:00
9eb3aad072 Fix SQLite bug: New invoice repeating 2018-03-29 11:57:17 +09:00
9355454953 Merge pull request #85 from pajasevi/lang-cs-fix
Fixed cs translation
2018-03-28 14:53:40 -05:00
6467f06c54 Fixed cs translation 2018-03-28 21:45:23 +02:00
b9b4b5ea39 log invoice event if Lightning max value exceeded 2018-03-28 23:15:10 +09:00
e23243565f Refactor CreateInvoiceCore to better give feedback on payment method errors to the merchant, be faster, and give NodeInfo 2018-03-28 22:37:01 +09:00
d3420532ae bump 2018-03-28 15:14:35 +09:00
ade1b9d4eb Merge pull request #84 from lepipele/dev-bugfix
Bugfixing currency icon positioning on smaller screens
2018-03-28 15:11:56 +09:00
fc278d12fc Bugfixing currency icon positioning on smaller screens 2018-03-28 01:09:53 -05:00
8e5ec822dc Powered by BTCPay Server 2018-03-27 15:22:48 +09:00
26aac66a76 Allow merchant to customize their checkout page 2018-03-27 15:14:50 +09:00
a562e90bdb Separate Checkout Experience settings from General store settings 2018-03-27 14:48:32 +09:00
a0f3698701 bump 2018-03-27 11:21:06 +09:00
02163f9482 Rewrite CanParseDerivationScheme 2018-03-27 11:21:06 +09:00
b74fe171e2 Merge pull request #83 from lepipele/master
Bugfixing isLightning compare for Conversion tab
2018-03-27 10:39:56 +09:00
2785bb4d9b Bugfixing isLightning compare for Conversion tab 2018-03-26 15:02:53 -05:00
5eac84d3a3 Fix bug: bitcoinAddress field of Invoice was showing ligthning BOLT11 address 2018-03-26 12:38:14 +09:00
a0a2ab6fcd update publish-docker 2018-03-26 11:54:10 +09:00
30 changed files with 469 additions and 286 deletions

View File

@ -841,27 +841,29 @@ namespace BTCPayServer.Tests
result = parser.Parse("xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X");
Assert.Equal("tpubD6NzVbkrYhZ4YSg7vGdAX6wxE8NwDrmih9SR6cK7gUtsAg37w5LfFpJgviCxC6bGGT4G3uckqH5fiV9ZLN1gm5qgQLVuymzFUR5ed7U7ksu-[legacy]", result.ToString());
////////////////
result = parser.Parse("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o");
Assert.Equal("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o", result.ToString());
var tpub = "tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o";
result = parser.Parse(tpub);
Assert.Equal(tpub, result.ToString());
parser.HintScriptPubKey = BitcoinAddress.Create("tb1q4s33amqm8l7a07zdxcunqnn3gcsjcfz3xc573l", parser.Network).ScriptPubKey;
result = parser.Parse("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o");
Assert.Equal("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o", result.ToString());
result = parser.Parse(tpub);
Assert.Equal(tpub, result.ToString());
parser.HintScriptPubKey = BitcoinAddress.Create("2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ", parser.Network).ScriptPubKey;
result = parser.Parse("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o");
Assert.Equal("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o-[p2sh]", result.ToString());
result = parser.Parse(tpub);
Assert.Equal($"{tpub}-[p2sh]", result.ToString());
parser.HintScriptPubKey = BitcoinAddress.Create("mwD8bHS65cdgUf6rZUUSoVhi3wNQFu1Nfi", parser.Network).ScriptPubKey;
result = parser.Parse("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o");
Assert.Equal("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o-[legacy]", result.ToString());
result = parser.Parse(tpub);
Assert.Equal($"{tpub}-[legacy]", result.ToString());
parser.HintScriptPubKey = BitcoinAddress.Create("2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ", parser.Network).ScriptPubKey;
result = parser.Parse("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o-[legacy]");
Assert.Equal("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o-[p2sh]", result.ToString());
result = parser.Parse($"{tpub}-[legacy]");
Assert.Equal($"{tpub}-[p2sh]", result.ToString());
result = parser.Parse("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o");
Assert.Equal("tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o-[p2sh]", result.ToString());
result = parser.Parse(tpub);
Assert.Equal($"{tpub}-[p2sh]", result.ToString());
}
[Fact]

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.1.65</Version>
<Version>1.0.1.70</Version>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@ -213,6 +213,8 @@ namespace BTCPayServer.Controllers
OrderId = invoice.OrderId,
InvoiceId = invoice.Id,
DefaultLang = storeBlob.DefaultLang ?? "en-US",
CustomCSSLink = storeBlob.CustomCSS?.AbsoluteUri,
CustomLogoLink = storeBlob.CustomLogo?.AbsoluteUri,
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ToString(),
BtcDue = accounting.Due.ToString(),

View File

@ -79,28 +79,6 @@ namespace BTCPayServer.Controllers
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl)
{
var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
.Select(c =>
(Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler<>).MakeGenericType(c.GetType())),
SupportedPaymentMethod: c,
Network: _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode),
IsAvailable: Task.FromResult(false)))
.Where(c => c.Network != null)
.Select(c =>
{
c.IsAvailable = c.Handler.IsAvailable(c.SupportedPaymentMethod, c.Network);
return c;
})
.ToList();
foreach (var supportedPaymentMethod in supportedPaymentMethods.ToList())
{
if (!await supportedPaymentMethod.IsAvailable)
{
supportedPaymentMethods.Remove(supportedPaymentMethod);
}
}
if (supportedPaymentMethods.Count == 0)
throw new BitpayHttpException(400, "No derivation strategy are available now for this store");
var entity = new InvoiceEntity
{
InvoiceTime = DateTimeOffset.UtcNow
@ -132,61 +110,50 @@ namespace BTCPayServer.Controllers
entity.Status = "new";
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
var methods = supportedPaymentMethods
.Select(async o =>
{
var rate = await storeBlob.ApplyRateRules(o.Network, _RateProviders.GetRateProvider(o.Network, false)).GetRateAsync(invoice.Currency);
PaymentMethod paymentMethod = new PaymentMethod();
paymentMethod.ParentEntity = entity;
paymentMethod.Network = o.Network;
paymentMethod.SetId(o.SupportedPaymentMethod.PaymentId);
paymentMethod.Rate = rate;
var paymentDetails = await o.Handler.CreatePaymentMethodDetails(o.SupportedPaymentMethod, paymentMethod, o.Network);
if (storeBlob.NetworkFeeDisabled)
paymentDetails.SetNoTxFee();
paymentMethod.SetPaymentMethodDetails(paymentDetails);
#pragma warning disable CS0618
if (paymentMethod.GetId().IsBTCOnChain)
{
entity.TxFee = paymentMethod.TxFee;
entity.Rate = paymentMethod.Rate;
entity.DepositAddress = paymentMethod.DepositAddress;
}
#pragma warning restore CS0618
return (SupportedPaymentMethod: o.SupportedPaymentMethod, PaymentMethod: paymentMethod);
});
var paymentMethods = new PaymentMethodDictionary();
var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
.Select(c =>
(Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler<>).MakeGenericType(c.GetType())),
SupportedPaymentMethod: c,
Network: _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode)))
.Where(c => c.Network != null)
.Select(o =>
(SupportedPaymentMethod: o.SupportedPaymentMethod,
PaymentMethod: CreatePaymentMethodAsync(o.Handler, o.SupportedPaymentMethod, o.Network, entity, storeBlob)))
.ToList();
List<string> paymentMethodErrors = new List<string>();
List<ISupportedPaymentMethod> supported = new List<ISupportedPaymentMethod>();
foreach (var method in methods)
var paymentMethods = new PaymentMethodDictionary();
foreach (var o in supportedPaymentMethods)
{
var o = await method;
// Check if Lightning Max value is exceeded
if(o.SupportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike &&
storeBlob.LightningMaxValue != null)
try
{
var lightningMaxValue = storeBlob.LightningMaxValue;
decimal rate = 0.0m;
if (lightningMaxValue.Currency == invoice.Currency)
rate = o.PaymentMethod.Rate;
else
rate = await storeBlob.ApplyRateRules(o.PaymentMethod.Network, _RateProviders.GetRateProvider(o.PaymentMethod.Network, false)).GetRateAsync(lightningMaxValue.Currency);
var lightningMaxValueCrypto = Money.Coins(lightningMaxValue.Value / rate);
if (o.PaymentMethod.Calculate().Due > lightningMaxValueCrypto)
{
continue;
}
var paymentMethod = await o.PaymentMethod;
if (paymentMethod == null)
throw new PaymentMethodUnavailableException("Payment method unavailable (The handler returned null)");
supported.Add(o.SupportedPaymentMethod);
paymentMethods.Add(paymentMethod);
}
catch (PaymentMethodUnavailableException ex)
{
paymentMethodErrors.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Payment method unavailable ({ex.Message})");
}
catch (Exception ex)
{
paymentMethodErrors.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Unexpected exception ({ex.ToString()})");
}
///////////////
supported.Add(o.SupportedPaymentMethod);
paymentMethods.Add(o.PaymentMethod);
}
if(supported.Count == 0)
if (supported.Count == 0)
{
throw new BitpayHttpException(400, "No derivation strategy are available now for this store");
StringBuilder errors = new StringBuilder();
errors.AppendLine("No payment method available for this store");
foreach(var error in paymentMethodErrors)
{
errors.AppendLine(error);
}
throw new BitpayHttpException(400, errors.ToString());
}
entity.SetSupportedPaymentMethods(supported);
@ -209,12 +176,57 @@ namespace BTCPayServer.Controllers
#pragma warning restore CS0618
}
entity.PosData = invoice.PosData;
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, paymentMethodErrors, _NetworkProvider);
_EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
var resp = entity.EntityToDTO(_NetworkProvider);
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
}
private async Task<PaymentMethod> CreatePaymentMethodAsync(IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreBlob storeBlob)
{
var rate = await storeBlob.ApplyRateRules(network, _RateProviders.GetRateProvider(network, false)).GetRateAsync(entity.ProductInformation.Currency);
PaymentMethod paymentMethod = new PaymentMethod();
paymentMethod.ParentEntity = entity;
paymentMethod.Network = network;
paymentMethod.SetId(supportedPaymentMethod.PaymentId);
paymentMethod.Rate = rate;
var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, network);
if (storeBlob.NetworkFeeDisabled)
paymentDetails.SetNoTxFee();
paymentMethod.SetPaymentMethodDetails(paymentDetails);
// Check if Lightning Max value is exceeded
if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike &&
storeBlob.LightningMaxValue != null)
{
var lightningMaxValue = storeBlob.LightningMaxValue;
var lightningMaxValueRate = 0.0m;
if (lightningMaxValue.Currency == entity.ProductInformation.Currency)
lightningMaxValueRate = paymentMethod.Rate;
else
lightningMaxValueRate = await storeBlob.ApplyRateRules(network, _RateProviders.GetRateProvider(network, false)).GetRateAsync(lightningMaxValue.Currency);
var lightningMaxValueCrypto = Money.Coins(lightningMaxValue.Value / lightningMaxValueRate);
if (paymentMethod.Calculate().Due > lightningMaxValueCrypto)
{
throw new PaymentMethodUnavailableException("Lightning max value exceeded");
}
}
///////////////
#pragma warning disable CS0618
if (paymentMethod.GetId().IsBTCOnChain)
{
entity.TxFee = paymentMethod.TxFee;
entity.Rate = paymentMethod.Rate;
entity.DepositAddress = paymentMethod.DepositAddress;
}
#pragma warning restore CS0618
return paymentMethod;
}
#pragma warning disable CS0618
private static Money GetTxFee(StoreBlob storeBlob, FeeRate feeRate)
{

View File

@ -183,6 +183,69 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(StoreUsers), new { storeId = storeId, userId = userId });
}
[HttpGet]
[Route("{storeId}/checkout")]
public async Task<IActionResult> CheckoutExperience(string storeId)
{
var store = await _Repo.FindStore(storeId, GetUserId());
if (store == null)
return NotFound();
var storeBlob = store.GetStoreBlob();
var vm = new CheckoutExperienceViewModel();
vm.SetCryptoCurrencies(_ExplorerProvider, store.GetDefaultCrypto());
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
vm.AllowCoinConversion = storeBlob.AllowCoinConversion;
vm.CustomCSS = storeBlob.CustomCSS;
vm.CustomLogo = storeBlob.CustomLogo;
return View(vm);
}
[HttpPost]
[Route("{storeId}/checkout")]
public async Task<IActionResult> CheckoutExperience(string storeId, CheckoutExperienceViewModel model)
{
CurrencyValue currencyValue = null;
if (!string.IsNullOrWhiteSpace(model.LightningMaxValue))
{
if (!CurrencyValue.TryParse(model.LightningMaxValue, out currencyValue))
{
ModelState.AddModelError(nameof(model.LightningMaxValue), "Invalid currency value");
}
}
var store = await _Repo.FindStore(storeId, GetUserId());
if (store == null)
return NotFound();
bool needUpdate = false;
var blob = store.GetStoreBlob();
if (store.GetDefaultCrypto() != model.DefaultCryptoCurrency)
{
needUpdate = true;
store.SetDefaultCrypto(model.DefaultCryptoCurrency);
}
model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency);
model.SetLanguages(_LangService, model.DefaultLang);
blob.DefaultLang = model.DefaultLang;
blob.AllowCoinConversion = model.AllowCoinConversion;
blob.LightningMaxValue = currencyValue;
blob.CustomLogo = model.CustomLogo;
blob.CustomCSS = model.CustomCSS;
if (store.SetStoreBlob(blob))
{
needUpdate = true;
}
if (needUpdate)
{
await _Repo.UpdateStore(store);
StatusMessage = "Store successfully updated";
}
return RedirectToAction(nameof(CheckoutExperience), new
{
storeId = storeId
});
}
[HttpGet]
[Route("{storeId}")]
public async Task<IActionResult> UpdateStore(string storeId)
@ -195,19 +258,14 @@ namespace BTCPayServer.Controllers
var vm = new StoreViewModel();
vm.Id = store.Id;
vm.StoreName = store.StoreName;
vm.SetCryptoCurrencies(_ExplorerProvider, store.GetDefaultCrypto());
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
vm.StoreWebsite = store.StoreWebsite;
vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
vm.SpeedPolicy = store.SpeedPolicy;
AddPaymentMethods(store, vm);
vm.StatusMessage = StatusMessage;
vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
vm.RateMultiplier = (double)storeBlob.GetRateMultiplier();
vm.PreferredExchange = storeBlob.PreferredExchange.IsCoinAverage() ? "coinaverage" : storeBlob.PreferredExchange;
vm.AllowCoinConversion = storeBlob.AllowCoinConversion;
return View(vm);
}
@ -249,14 +307,6 @@ namespace BTCPayServer.Controllers
[Route("{storeId}")]
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model)
{
CurrencyValue currencyValue = null;
if (!string.IsNullOrWhiteSpace(model.LightningMaxValue))
{
if(!CurrencyValue.TryParse(model.LightningMaxValue, out currencyValue))
{
ModelState.AddModelError(nameof(model.LightningMaxValue), "Invalid currency value");
}
}
if (!ModelState.IsValid)
{
return View(model);
@ -285,26 +335,15 @@ namespace BTCPayServer.Controllers
store.StoreWebsite = model.StoreWebsite;
}
if (store.GetDefaultCrypto() != model.DefaultCryptoCurrency)
{
needUpdate = true;
store.SetDefaultCrypto(model.DefaultCryptoCurrency);
}
model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency);
model.SetLanguages(_LangService, model.DefaultLang);
var blob = store.GetStoreBlob();
blob.NetworkFeeDisabled = !model.NetworkFee;
blob.MonitoringExpiration = model.MonitoringExpiration;
blob.InvoiceExpiration = model.InvoiceExpiration;
blob.DefaultLang = model.DefaultLang;
blob.LightningMaxValue = currencyValue;
bool newExchange = blob.PreferredExchange != model.PreferredExchange;
blob.PreferredExchange = model.PreferredExchange;
blob.SetRateMultiplier(model.RateMultiplier);
blob.AllowCoinConversion = model.AllowCoinConversion;
if (store.SetStoreBlob(blob))
{

View File

@ -258,6 +258,11 @@ namespace BTCPayServer.Data
[JsonConverter(typeof(CurrencyValueJsonConverter))]
public CurrencyValue LightningMaxValue { get; set; }
[JsonConverter(typeof(UriJsonConverter))]
public Uri CustomLogo { get; set; }
[JsonConverter(typeof(UriJsonConverter))]
public Uri CustomCSS { get; set; }
public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider)
{
if (!PreferredExchange.IsCoinAverage())

View File

@ -15,7 +15,6 @@ namespace BTCPayServer.JsonConverters
return typeof(CurrencyValue).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
Type longType = typeof(long).GetTypeInfo();
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Reflection;
using Newtonsoft.Json;
using NBitcoin.JsonConverters;
namespace BTCPayServer.JsonConverters
{
public class UriJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Uri).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
return reader.TokenType == JsonToken.Null ? null :
Uri.TryCreate((string)reader.Value, UriKind.Absolute, out var result) ? result :
throw new JsonObjectException("Invalid Currency value", reader);
}
catch (InvalidCastException)
{
throw new JsonObjectException("Invalid Currency value", reader);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null)
writer.WriteValue(((Uri)value).AbsoluteUri);
}
}
}

View File

@ -13,7 +13,8 @@ namespace BTCPayServer.Models.InvoicingModels
public string CryptoImage { get; set; }
public string Link { get; set; }
}
public string CustomCSSLink { get; set; }
public string CustomLogoLink { get; set; }
public string DefaultLang { get; set; }
public List<AvailableCrypto> AvailableCryptos { get; set; } = new List<AvailableCrypto>();
public bool IsLightning { get; set; }

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Models.StoreViewModels
{
public class CheckoutExperienceViewModel
{
class Format
{
public string Name { get; set; }
public string Value { get; set; }
}
public SelectList CryptoCurrencies { get; set; }
public SelectList Languages { get; set; }
[Display(Name = "Default crypto currency on checkout")]
public string DefaultCryptoCurrency { get; set; }
[Display(Name = "Default language on checkout")]
public string DefaultLang { get; set; }
[Display(Name = "Allow conversion through third party (Shapeshift, Changelly...)")]
public bool AllowCoinConversion
{
get; set;
}
[Display(Name = "Do not propose lightning payment if value of the invoice is above...")]
[MaxLength(20)]
public string LightningMaxValue { get; set; }
[Display(Name = "Link to a custom CSS stylesheet")]
[Url]
public Uri CustomCSS { get; set; }
[Display(Name = "Link to a custom logo")]
[Url]
public Uri CustomLogo { get; set; }
public void SetCryptoCurrencies(ExplorerClientProvider explorerProvider, string defaultCrypto)
{
var choices = explorerProvider.GetAll().Select(o => new Format() { Name = o.Item1.CryptoCode, Value = o.Item1.CryptoCode }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultCrypto) ?? choices.FirstOrDefault();
CryptoCurrencies = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
DefaultCryptoCurrency = chosen.Name;
}
public void SetLanguages(LanguageService langService, string defaultLang)
{
defaultLang = defaultLang ?? "en-US";
var choices = langService.GetLanguages().Select(o => new Format() { Name = o.DisplayName, Value = o.Code }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultLang) ?? choices.FirstOrDefault();
Languages = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
DefaultLang = chosen.Value;
}
}
}

View File

@ -17,11 +17,7 @@ namespace BTCPayServer.Models.StoreViewModels
public string Crypto { get; set; }
public string Value { get; set; }
}
class Format
{
public string Name { get; set; }
public string Value { get; set; }
}
public StoreViewModel()
{
@ -83,11 +79,6 @@ namespace BTCPayServer.Models.StoreViewModels
set;
}
[Display(Name = "Do not propose lightning payment if value of the invoice is above...")]
[MaxLength(20)]
public string LightningMaxValue { get; set; }
[Display(Name = "Consider the invoice confirmed when the payment transaction...")]
public SpeedPolicy SpeedPolicy
{
@ -100,24 +91,6 @@ namespace BTCPayServer.Models.StoreViewModels
get; set;
}
[Display(Name = "Allow conversion through third party (Shapeshift, Changelly...)")]
public bool AllowCoinConversion
{
get; set;
}
public string StatusMessage
{
get; set;
}
public SelectList CryptoCurrencies { get; set; }
public SelectList Languages { get; set; }
[Display(Name = "Default crypto currency on checkout")]
public string DefaultCryptoCurrency { get; set; }
[Display(Name = "Default language on checkout")]
public string DefaultLang { get; set; }
public class LightningNode
{
public string CryptoCode { get; set; }
@ -128,21 +101,5 @@ namespace BTCPayServer.Models.StoreViewModels
get; set;
} = new List<LightningNode>();
public void SetCryptoCurrencies(ExplorerClientProvider explorerProvider, string defaultCrypto)
{
var choices = explorerProvider.GetAll().Select(o => new Format() { Name = o.Item1.CryptoCode, Value = o.Item1.CryptoCode }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultCrypto) ?? choices.FirstOrDefault();
CryptoCurrencies = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
DefaultCryptoCurrency = chosen.Name;
}
public void SetLanguages(LanguageService langService, string defaultLang)
{
defaultLang = defaultLang ?? "en-US";
var choices = langService.GetLanguages().Select(o => new Format() { Name = o.DisplayName, Value = o.Code }).ToArray();
var chosen = choices.FirstOrDefault(f => f.Value == defaultLang) ?? choices.FirstOrDefault();
Languages = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
DefaultLang = chosen.Value;
}
}
}

View File

@ -29,6 +29,8 @@ namespace BTCPayServer.Payments.Bitcoin
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(DerivationStrategy supportedPaymentMethod, PaymentMethod paymentMethod, BTCPayNetwork network)
{
if (!_ExplorerProvider.IsAvailable(network))
throw new PaymentMethodUnavailableException($"Full node not available");
var getFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync();
var getAddress = _WalletProvider.GetWallet(network).ReserveAddressAsync(supportedPaymentMethod.DerivationStrategyBase);
Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod = new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod();
@ -37,10 +39,5 @@ namespace BTCPayServer.Payments.Bitcoin
onchainMethod.DepositAddress = (await getAddress).ToString();
return onchainMethod;
}
public override Task<bool> IsAvailable(DerivationStrategy supportedPaymentMethod, BTCPayNetwork network)
{
return Task.FromResult(_ExplorerProvider.IsAvailable(network));
}
}
}

View File

@ -11,14 +11,6 @@ namespace BTCPayServer.Payments
/// </summary>
public interface IPaymentMethodHandler
{
/// <summary>
/// Returns true if the dependencies for a specific payment method are satisfied.
/// </summary>
/// <param name="supportedPaymentMethod"></param>
/// <param name="network"></param>
/// <returns>true if this payment method is available</returns>
Task<bool> IsAvailable(ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network);
/// <summary>
/// Create needed to track payments of this invoice
/// </summary>
@ -31,7 +23,6 @@ namespace BTCPayServer.Payments
public interface IPaymentMethodHandler<T> : IPaymentMethodHandler where T : ISupportedPaymentMethod
{
Task<bool> IsAvailable(T supportedPaymentMethod, BTCPayNetwork network);
Task<IPaymentMethodDetails> CreatePaymentMethodDetails(T supportedPaymentMethod, PaymentMethod paymentMethod, BTCPayNetwork network);
}
@ -47,16 +38,5 @@ namespace BTCPayServer.Payments
}
throw new NotSupportedException("Invalid supportedPaymentMethod");
}
public abstract Task<bool> IsAvailable(T supportedPaymentMethod, BTCPayNetwork network);
Task<bool> IPaymentMethodHandler.IsAvailable(ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
{
if(supportedPaymentMethod is T method)
{
return IsAvailable(method, network);
}
return Task.FromResult(false);
}
}
}

View File

@ -7,7 +7,7 @@ using Newtonsoft.Json;
namespace BTCPayServer.Payments.Lightning.CLightning
{
public class CreateInvoiceResponse
public class CLightningInvoice
{
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
[JsonProperty("payment_hash")]

View File

@ -7,7 +7,6 @@ using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Payments.Lightning.Charge;
using Mono.Unix;
using NBitcoin;
using NBitcoin.RPC;
@ -42,9 +41,9 @@ namespace BTCPayServer.Payments.Lightning.CLightning
Network = network;
}
public Task<GetInfoResponse> GetInfoAsync(CancellationToken cancellation = default(CancellationToken))
public Task<Charge.GetInfoResponse> GetInfoAsync(CancellationToken cancellation = default(CancellationToken))
{
return SendCommandAsync<GetInfoResponse>("getinfo", cancellation: cancellation);
return SendCommandAsync<Charge.GetInfoResponse>("getinfo", cancellation: cancellation);
}
public Task SendAsync(string bolt11)
@ -168,24 +167,24 @@ namespace BTCPayServer.Payments.Lightning.CLightning
async Task<LightningInvoice> ILightningInvoiceClient.GetInvoice(string invoiceId, CancellationToken cancellation)
{
var invoices = await SendCommandAsync<ChargeInvoice[]>("listinvoices", new[] { invoiceId }, false, true, cancellation);
var invoices = await SendCommandAsync<CLightningInvoice[]>("listinvoices", new[] { invoiceId }, false, true, cancellation);
if (invoices.Length == 0)
return null;
return ChargeClient.ToLightningInvoice(invoices[0]);
return ToLightningInvoice(invoices[0]);
}
static NBitcoin.DataEncoders.DataEncoder InvoiceIdEncoder = NBitcoin.DataEncoders.Encoders.Base58;
async Task<LightningInvoice> ILightningInvoiceClient.CreateInvoice(LightMoney amount, TimeSpan expiry, CancellationToken cancellation)
{
var id = InvoiceIdEncoder.EncodeData(RandomUtils.GetBytes(20));
var invoice = await SendCommandAsync<CreateInvoiceResponse>("invoice", new object[] { amount.MilliSatoshi, id, "" }, cancellation: cancellation);
var invoice = await SendCommandAsync<CLightningInvoice>("invoice", new object[] { amount.MilliSatoshi, id, "" }, cancellation: cancellation);
invoice.Label = id;
invoice.MilliSatoshi = amount;
invoice.Status = "unpaid";
return ToLightningInvoice(invoice);
}
private static LightningInvoice ToLightningInvoice(CreateInvoiceResponse invoice)
private static LightningInvoice ToLightningInvoice(CLightningInvoice invoice)
{
return new LightningInvoice()
{
@ -204,9 +203,9 @@ namespace BTCPayServer.Payments.Lightning.CLightning
long lastInvoiceIndex = 99999999999;
async Task<LightningInvoice> ILightningListenInvoiceSession.WaitInvoice(CancellationToken cancellation)
{
var chargeInvoice = await SendCommandAsync<CreateInvoiceResponse>("waitanyinvoice", new object[] { lastInvoiceIndex }, cancellation: cancellation);
lastInvoiceIndex = chargeInvoice.PayIndex.Value;
return ToLightningInvoice(chargeInvoice);
var invoice = await SendCommandAsync<CLightningInvoice>("waitanyinvoice", new object[] { lastInvoiceIndex }, cancellation: cancellation);
lastInvoiceIndex = invoice.PayIndex.Value;
return ToLightningInvoice(invoice);
}
async Task<LightningNodeInformation> ILightningInvoiceClient.GetInfo(CancellationToken cancellation)

View File

@ -25,30 +25,32 @@ namespace BTCPayServer.Payments.Lightning
}
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, BTCPayNetwork network)
{
var test = Test(supportedPaymentMethod, network);
var invoice = paymentMethod.ParentEntity;
var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, 8);
var client = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
if (expiry < TimeSpan.Zero)
expiry = TimeSpan.FromSeconds(1);
var lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), expiry);
LightningInvoice lightningInvoice = null;
try
{
lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), expiry);
}
catch(Exception ex)
{
throw new PaymentMethodUnavailableException($"Impossible to create lightning invoice ({ex.Message})", ex);
}
var nodeInfo = await test;
return new LightningLikePaymentMethodDetails()
{
BOLT11 = lightningInvoice.BOLT11,
InvoiceId = lightningInvoice.Id
InvoiceId = lightningInvoice.Id,
NodeInfo = nodeInfo.ToString()
};
}
public async override Task<bool> IsAvailable(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
{
try
{
await Test(supportedPaymentMethod, network);
return true;
}
catch { return false; }
}
/// <summary>
/// Used for testing
/// </summary>
@ -57,8 +59,8 @@ namespace BTCPayServer.Payments.Lightning
public async Task<NodeInfo> Test(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
{
if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
throw new Exception($"Full node not available");
throw new PaymentMethodUnavailableException($"Full node not available");
var cts = new CancellationTokenSource(5000);
var client = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
LightningNodeInformation info = null;
@ -68,37 +70,39 @@ namespace BTCPayServer.Payments.Lightning
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
throw new Exception($"The lightning node did not replied in a timely maner");
throw new PaymentMethodUnavailableException($"The lightning node did not replied in a timely maner");
}
catch (Exception ex)
{
throw new Exception($"Error while connecting to the API ({ex.Message})");
throw new PaymentMethodUnavailableException($"Error while connecting to the API ({ex.Message})");
}
if(info.Address == null)
if (info.Address == null)
{
throw new Exception($"No lightning node public address has been configured");
throw new PaymentMethodUnavailableException($"No lightning node public address has been configured");
}
var blocksGap = Math.Abs(info.BlockHeight - summary.Status.ChainHeight);
if (blocksGap > 10)
{
throw new Exception($"The lightning is not synched ({blocksGap} blocks)");
throw new PaymentMethodUnavailableException($"The lightning is not synched ({blocksGap} blocks)");
}
try
{
if(!SkipP2PTest)
if (!SkipP2PTest)
{
await TestConnection(info.Address, info.P2PPort, cts.Token);
}
}
catch (Exception ex)
{
throw new Exception($"Error while connecting to the lightning node via {info.Address}:{info.P2PPort} ({ex.Message})");
throw new PaymentMethodUnavailableException($"Error while connecting to the lightning node via {info.Address}:{info.P2PPort} ({ex.Message})");
}
return new NodeInfo(info.NodeId, info.Address, info.P2PPort);
}
private async Task<bool> TestConnection(string addressStr, int port, CancellationToken cancellation)
private async Task TestConnection(string addressStr, int port, CancellationToken cancellation)
{
IPAddress address = null;
try
@ -107,25 +111,16 @@ namespace BTCPayServer.Payments.Lightning
}
catch
{
try
{
address = (await Dns.GetHostAddressesAsync(addressStr)).FirstOrDefault();
}
catch { }
address = (await Dns.GetHostAddressesAsync(addressStr)).FirstOrDefault();
}
if (address == null)
throw new Exception($"DNS did not resolved {addressStr}");
throw new PaymentMethodUnavailableException($"DNS did not resolved {addressStr}");
using (var tcp = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
{
try
{
await WithTimeout(tcp.ConnectAsync(new IPEndPoint(address, port)), cancellation);
}
catch { return false; }
await WithTimeout(tcp.ConnectAsync(new IPEndPoint(address, port)), cancellation);
}
return true;
}
static Task WithTimeout(Task task, CancellationToken token)

View File

@ -9,6 +9,7 @@ namespace BTCPayServer.Payments.Lightning
{
public string BOLT11 { get; set; }
public string InvoiceId { get; set; }
public string NodeInfo { get; set; }
public string GetPaymentDestination()
{

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Payments
{
public class PaymentMethodUnavailableException : Exception
{
public PaymentMethodUnavailableException(string message) : base(message)
{
}
public PaymentMethodUnavailableException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -375,7 +375,8 @@ namespace BTCPayServer.Services.Invoices
BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}",
};
}
if (info.GetId().PaymentType == PaymentTypes.LightningLike)
var paymentId = info.GetId();
if (paymentId.PaymentType == PaymentTypes.LightningLike)
{
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
{
@ -383,7 +384,7 @@ namespace BTCPayServer.Services.Invoices
};
}
#pragma warning disable CS0618
if (info.CryptoCode == "BTC")
if (info.CryptoCode == "BTC" && paymentId.PaymentType == PaymentTypes.BTCLike)
{
dto.Url = cryptoInfo.Url;
dto.BTCPrice = cryptoInfo.Price;

View File

@ -101,7 +101,7 @@ namespace BTCPayServer.Services.Invoices
}
}
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, BTCPayNetworkProvider networkProvider)
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, IEnumerable<string> creationLogs, BTCPayNetworkProvider networkProvider)
{
List<string> textSearch = new List<string>();
invoice = Clone(invoice, null);
@ -146,6 +146,17 @@ namespace BTCPayServer.Services.Invoices
textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
}
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id });
foreach(var log in creationLogs)
{
context.InvoiceEvents.Add(new InvoiceEventData()
{
InvoiceDataId = invoice.Id,
Message = log,
Timestamp = invoice.InvoiceTime,
UniqueId = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10))
});
}
await context.SaveChangesAsync().ConfigureAwait(false);
}

View File

@ -3,7 +3,14 @@
<div class="top-header">
<div class="header">
<div class="header__icon">
<img class="header__icon__img" src="~/img/logo-white.png" height="40">
@if(Model.CustomLogoLink != null)
{
<img class="header__icon__img" src="@Model.CustomLogoLink" height="40">
}
else
{
<img class="header__icon__img" src="~/img/logo-white.png" height="40">
}
</div>
</div>
<div class="timer-row">
@ -26,7 +33,7 @@
</div>
</div>
<div class="order-details">
@if (Model.AvailableCryptos.Count > 1)
@if(Model.AvailableCryptos.Count > 1)
{
<div class="currency-selection">
<div class="single-item-order__left">
@ -36,7 +43,7 @@
</div>
<div class="single-item-order__right">
<div class="payment__currencies">
@foreach (var crypto in Model.AvailableCryptos)
@foreach(var crypto in Model.AvailableCryptos)
{
<a href="@crypto.Link" onclick="return changeCurrency('@crypto.PaymentMethodId');">
<img style="height:32px; margin-left:5px;" alt="@crypto.PaymentMethodId" src="@crypto.CryptoImage" />
@ -101,7 +108,7 @@
<div class="payment-tabs__tab" id="copy-tab">
<span>{{$t("Copy")}}</span>
</div>
@if (Model.AllowCoinConversion)
@if(Model.AllowCoinConversion)
{
<div class="payment-tabs__tab" id="altcoins-tab">
<span>{{$t("Conversion")}}</span>
@ -142,7 +149,7 @@
</div>
<div class="bp-view payment scan" id="scan">
<div class="payment__scan">
<img v-bind:src="srvModel.cryptoImage" style="position: absolute; height:64px; width:64px; left:118px; top:96px;" />
<img v-bind:src="srvModel.cryptoImage" class="qr_currency_icon" />
<qrcode v-bind:val="srvModel.invoiceBitcoinUrlQR" v-bind:size="256" bg-color="#f5f5f7" fg-color="#000">
</qrcode>
</div>
@ -193,10 +200,17 @@
</div>
</div>
</div>
@if (Model.AllowCoinConversion)
@if(Model.AllowCoinConversion)
{
<div id="altcoins" class="bp-view payment manual-flow">
<div v-if="srvModel.isLightning">
<div class="manual__step-two__instructions">
<span>
{{$t("ConversionTab_Lightning")}}
</span>
</div>
</div>
<div v-else>
<div class="manual__step-two__instructions">
<span>
{{$t("ConversionTab_BodyTop", srvModel)}}
@ -217,13 +231,6 @@
</a>*@
</center>
</div>
<div v-else>
<div class="manual__step-two__instructions">
<span>
{{$t("ConversionTab_Lightning")}}
</span>
</div>
</div>
</div>
}

View File

@ -20,6 +20,12 @@
</script>
<bundle name="wwwroot/bundles/checkout-bundle.min.js" />
@if(Model.CustomCSSLink != null)
{
<link href="@Model.CustomCSSLink" rel="stylesheet" />
}
</head>
<body style="background: #E4E4E4">
<noscript>
@ -83,6 +89,9 @@
});
</script>
</div>
<div style="margin-top: 10px; text-align: right;" class="form-text small text-muted">
<span>Powered by <a target="_blank" href="https://github.com/btcpayserver/btcpayserver">BTCPay Server</a></span>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,54 @@
@model CheckoutExperienceViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["Title"] = "Checkout experience";
ViewData.AddActivePage(BTCPayServer.Views.Stores.StoreNavPages.Checkout);
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", TempData["TempDataProperty-StatusMessage"])
<div class="row">
<div class="col-md-6">
<div asp-validation-summary="All" class="text-danger"></div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<form method="post">
<div class="form-group">
<label asp-for="CustomLogo"></label>
<input asp-for="CustomLogo" class="form-control" />
<span asp-validation-for="CustomLogo" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CustomCSS"></label>
<input asp-for="CustomCSS" class="form-control" />
<span asp-validation-for="CustomCSS" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DefaultCryptoCurrency"></label>
<select asp-for="DefaultCryptoCurrency" asp-items="Model.CryptoCurrencies" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="DefaultLang"></label>
<select asp-for="DefaultLang" asp-items="Model.Languages" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="AllowCoinConversion"></label>
<input asp-for="AllowCoinConversion" type="checkbox" class="form-check" />
</div>
<div class="form-group">
<label asp-for="LightningMaxValue"></label>
<input asp-for="LightningMaxValue" class="form-control" />
<span asp-validation-for="LightningMaxValue" class="text-danger"></span>
<p class="form-text text-muted">Example: 5.50 USD</p>
</div>
<button name="command" type="submit" class="btn btn-success" value="Save">Save</button>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}

View File

@ -11,13 +11,14 @@ namespace BTCPayServer.Views.Stores
{
public static string ActivePageKey => "ActivePage";
public static string Index => "Index";
public static string Checkout => "Checkout experience";
public static string Tokens => "Tokens";
public static string Users => "Users";
public static string UsersNavClass(ViewContext viewContext) => PageNavClass(viewContext, Users);
public static string TokensNavClass(ViewContext viewContext) => PageNavClass(viewContext, Tokens);
public static string CheckoutNavClass(ViewContext viewContext) => PageNavClass(viewContext, Checkout);
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string PageNavClass(ViewContext viewContext, string page)

View File

@ -6,7 +6,7 @@
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", Model.StatusMessage)
@Html.Partial("_StatusMessage", TempData["TempDataProperty-StatusMessage"])
<div class="row">
<div class="col-md-6">
@ -30,14 +30,6 @@
<input asp-for="StoreWebsite" class="form-control" />
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DefaultCryptoCurrency"></label>
<select asp-for="DefaultCryptoCurrency" asp-items="Model.CryptoCurrencies" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="DefaultLang"></label>
<select asp-for="DefaultLang" asp-items="Model.Languages" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="NetworkFee"></label>
<input asp-for="NetworkFee" type="checkbox" class="form-check" />
@ -74,10 +66,6 @@
</select>
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="AllowCoinConversion"></label>
<input asp-for="AllowCoinConversion" type="checkbox" class="form-check" />
</div>
<div class="form-group">
<h5>Derivation Scheme</h5>
<span>The DerivationScheme represents the destination of the funds received by your invoice on chain.</span>
@ -94,19 +82,19 @@
</thead>
<tbody>
@foreach(var scheme in Model.DerivationSchemes)
{
<tr>
<td>@scheme.Crypto</td>
<td style="max-width:300px;overflow:hidden;">@scheme.Value</td>
<td style="text-align:right">
@if(!string.IsNullOrWhiteSpace(scheme.Value))
{
<a asp-action="Wallet" asp-route-cryptoCode="@scheme.Crypto">Wallet</a><span> - </span>
}
<a asp-action="AddDerivationScheme" asp-route-cryptoCode="@scheme.Crypto">Modify</a>
</td>
</tr>
}
{
<tr>
<td>@scheme.Crypto</td>
<td style="max-width:300px;overflow:hidden;">@scheme.Value</td>
<td style="text-align:right">
@if(!string.IsNullOrWhiteSpace(scheme.Value))
{
<a asp-action="Wallet" asp-route-cryptoCode="@scheme.Crypto">Wallet</a><span> - </span>
}
<a asp-action="AddDerivationScheme" asp-route-cryptoCode="@scheme.Crypto">Modify</a>
</td>
</tr>
}
</tbody>
</table>
</div>
@ -119,12 +107,6 @@
<span>This is experimental and not advised for production.</span>
</p>
</div>
<div class="form-group">
<label asp-for="LightningMaxValue"></label>
<input asp-for="LightningMaxValue" class="form-control" />
<span asp-validation-for="LightningMaxValue" class="text-danger"></span>
<p class="form-text text-muted">Example: 5.50 USD</p>
</div>
<div class="form-group">
<table class="table">
<thead class="thead-inverse">
@ -136,13 +118,13 @@
</thead>
<tbody>
@foreach(var scheme in Model.LightningNodes)
{
<tr>
<td>@scheme.CryptoCode</td>
<td>@scheme.Address</td>
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
</tr>
}
{
<tr>
<td>@scheme.CryptoCode</td>
<td>@scheme.Address</td>
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
</tr>
}
</tbody>
</table>
</div>

View File

@ -2,7 +2,8 @@
@inject SignInManager<ApplicationUser> SignInManager
<ul class="nav nav-pills nav-stacked">
<li class="@StoreNavPages.IndexNavClass(ViewContext)"><a asp-action="UpdateStore">Information</a></li>
<li class="@StoreNavPages.IndexNavClass(ViewContext)"><a asp-action="UpdateStore">General settings</a></li>
<li class="@StoreNavPages.CheckoutNavClass(ViewContext)"><a asp-action="CheckoutExperience">Checkout experience</a></li>
<li class="@StoreNavPages.TokensNavClass(ViewContext)"><a asp-action="ListTokens">Access Tokens</a></li>
<li class="@StoreNavPages.UsersNavClass(ViewContext)"><a asp-action="StoreUsers">Users</a></li>
</ul>

View File

@ -9883,6 +9883,17 @@ strong {
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.qr_currency_icon {
height: 64px;
width: 64px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
/* Warning Page */
.manual__step-two--warning {
display: block;

View File

@ -62,7 +62,7 @@ function onDataCallback(jsonData) {
}
// restoring qr code view only when currency is switched
if (jsonData.paymentMethodId == srvModel.paymentMethodId) {
if (jsonData.paymentMethodId === srvModel.paymentMethodId) {
$(".payment__currencies").show();
$(".payment__spinner").hide();
}
@ -72,7 +72,7 @@ function onDataCallback(jsonData) {
}
function changeCurrency(currency) {
if (srvModel.paymentMethodId != currency) {
if (srvModel.paymentMethodId !== currency) {
$(".payment__currencies").hide();
$(".payment__spinner").show();
srvModel.paymentMethodId = currency;

View File

@ -12,7 +12,7 @@ const locales_cs = {
"Order Amount": "Cena objednávky",
"Network Cost": "Síťové náklady",
"Already Paid": "Již zaplaceno",
"Due": "Do kdy",
"Due": "Zbývá",
// Tabs
"Scan": "Skenovat",
"Copy": "Kopírovat",

View File

@ -1,3 +1,6 @@
$ver = [regex]::Match((Get-Content BTCPayServer\BTCPayServer.csproj), '<Version>([^<]+)<').Groups[1].Value
git tag -a "v$ver" -m "$ver"
git push --tags
git push --tags
git tag -d "stable"
git tag -a "stable" -m "stable"
git push --tags --force