Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
0c8f37ca19 | |||
5dea0312ac | |||
f074007f67 | |||
88818ece29 | |||
fa0fa28949 | |||
08e31f6fe8 | |||
b976adeefe | |||
53c53b98e6 | |||
a171e00280 | |||
46f94d7175 | |||
2e555cac22 | |||
0d91b3286a | |||
396432b873 | |||
15c58434e8 | |||
daad1bdd25 | |||
c60966c725 | |||
fb57d8c3ce | |||
799ce74f65 | |||
8e38d7ceb4 | |||
a1c22e8071 | |||
6d8acf54d6 | |||
a500a89138 | |||
c6d44e7a89 | |||
9eb3aad072 | |||
9355454953 | |||
6467f06c54 | |||
b9b4b5ea39 | |||
e23243565f |
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<Version>1.0.1.69</Version>
|
||||
<Version>1.0.1.71</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
@ -21,6 +21,7 @@ using System.Threading;
|
||||
using BTCPayServer.Events;
|
||||
using NBXplorer;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -229,6 +230,7 @@ namespace BTCPayServer.Controllers
|
||||
InvoiceBitcoinUrl = paymentMethodId.PaymentType == PaymentTypes.BTCLike ? cryptoInfo.PaymentUrls.BIP21 :
|
||||
paymentMethodId.PaymentType == PaymentTypes.LightningLike ? cryptoInfo.PaymentUrls.BOLT11 :
|
||||
throw new NotSupportedException(),
|
||||
PeerInfo = (paymentMethodDetails as LightningLikePaymentMethodDetails)?.NodeInfo,
|
||||
InvoiceBitcoinUrlQR = paymentMethodId.PaymentType == PaymentTypes.BTCLike ? cryptoInfo.PaymentUrls.BIP21 :
|
||||
paymentMethodId.PaymentType == PaymentTypes.LightningLike ? cryptoInfo.PaymentUrls.BOLT11.ToUpperInvariant() :
|
||||
throw new NotSupportedException(),
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -49,5 +49,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string PaymentMethodId { get; internal set; }
|
||||
|
||||
public bool AllowCoinConversion { get; set; }
|
||||
public string PeerInfo { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
{
|
||||
|
19
BTCPayServer/Payments/PaymentMethodUnavailableException.cs
Normal file
19
BTCPayServer/Payments/PaymentMethodUnavailableException.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ namespace BTCPayServer.Services
|
||||
new Language("pt-BR", "Portuguese (Brazil)"),
|
||||
new Language("nl-NL", "Dutch"),
|
||||
new Language("cs-CZ", "Česky"),
|
||||
new Language("is-IS", "Íslenska"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="top-header">
|
||||
<div class="header">
|
||||
<div class="header__icon">
|
||||
@if(Model.CustomLogoLink != null)
|
||||
@if (Model.CustomLogoLink != null)
|
||||
{
|
||||
<img class="header__icon__img" src="@Model.CustomLogoLink" height="40">
|
||||
}
|
||||
@ -33,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">
|
||||
@ -43,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" />
|
||||
@ -108,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>
|
||||
@ -160,57 +160,59 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bp-view payment manual-flow" id="copy">
|
||||
<div class="manual__step-two__instructions">
|
||||
<span i18n="">{{$t("CompletePay_Body", srvModel)}}</span>
|
||||
</div>
|
||||
<div class="manual-box flipped" style="margin-bottom: 30px;">
|
||||
<div class="manual-box__amount">
|
||||
<div class="manual-box__amount__label label">{{$t("Amount")}}</div>
|
||||
|
||||
<div class="manual-box__amount__value copy-cursor" ngxclipboard="">
|
||||
<span>{{srvModel.btcDue}}</span> {{ srvModel.cryptoCode }}
|
||||
<div class="copied-label">
|
||||
<span>{{$t("Copied")}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="manual-box__address">
|
||||
<div class="flipper flipped-initially">
|
||||
<div class="back"></div>
|
||||
<div class="front">
|
||||
<div class="manual-box__address__arrow"></div>
|
||||
<div class="manual-box__address__label label">{{$t("Address")}}</div>
|
||||
|
||||
<div class="manual-box__address__value copy-cursor" ngxclipboard="">
|
||||
<div class="manual-box__address__wrapper">
|
||||
<div class="manual-box__address__wrapper__logo">
|
||||
<img :src="srvModel.cryptoImage" height="16" />
|
||||
</div>
|
||||
<div class="manual-box__address__wrapper__value" style="overflow:hidden;max-width:240px;">{{srvModel.btcAddress}}</div>
|
||||
</div>
|
||||
<div class="copied-label" style="top: 5px;">
|
||||
<span>{{$t("Copied")}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="copyLabelPopup">
|
||||
<span>{{$t("Copied")}}</span>
|
||||
</div>
|
||||
<nav v-if="srvModel.isLightning" class="copyBox">
|
||||
<div class="copySectionBox bottomBorder">
|
||||
<label>{{$t("BOLT 11 Invoice")}}</label>
|
||||
<div class="inputWithIcon _copyInput">
|
||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.btcAddress" readonly="readonly" />
|
||||
<img v-bind:src="srvModel.cryptoImage" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="separatorGem"></div>
|
||||
<div class="copySectionBox">
|
||||
<label>{{$t("Peer Info")}}</label>
|
||||
<div class="inputWithIcon _copyInput">
|
||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.peerInfo" readonly="readonly" />
|
||||
<img v-bind:src="srvModel.cryptoImage" />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav v-else class="copyBox">
|
||||
<div class="copySectionBox bottomBorder">
|
||||
<label>{{$t("Amount")}}</label>
|
||||
<div class="copyAmountText copy-cursor _copySpan">
|
||||
<span>{{srvModel.btcDue}}</span> {{ srvModel.cryptoCode }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="separatorGem"></div>
|
||||
<div class="copySectionBox">
|
||||
<label>{{$t("Address")}}</label>
|
||||
<div class="inputWithIcon _copyInput">
|
||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.btcAddress" readonly="readonly" />
|
||||
<img v-bind:src="srvModel.cryptoImage" />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
@if(Model.AllowCoinConversion)
|
||||
@if (Model.AllowCoinConversion)
|
||||
{
|
||||
<div id="altcoins" class="bp-view payment manual-flow">
|
||||
<div v-if="srvModel.isLightning">
|
||||
<nav v-if="srvModel.isLightning">
|
||||
<div class="manual__step-two__instructions">
|
||||
<span>
|
||||
{{$t("ConversionTab_Lightning")}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
</nav>
|
||||
<nav v-else>
|
||||
<div class="manual__step-two__instructions">
|
||||
<span>
|
||||
{{$t("ConversionTab_BodyTop", srvModel)}}
|
||||
@ -230,7 +232,7 @@
|
||||
<img src="https://changelly.com/pay_button_pay_with.png" alt="Changelly" />
|
||||
</a>*@
|
||||
</center>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,8 @@
|
||||
'fr-FR': { translation: locales_fr },
|
||||
'pt-BR': { translation: locales_pt_br },
|
||||
'nl': { translation: locales_nl },
|
||||
'cs-CZ': { translation: locales_cs }
|
||||
'cs-CZ': { translation: locales_cs },
|
||||
'is-IS': { translation: locales_is }
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -8625,10 +8625,10 @@ strong {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.copy-cursor {
|
||||
cursor: copy;
|
||||
}
|
||||
|
||||
/*
|
||||
After refactoring Copy tab, this section until EOF REFACTOR can likely be deleted
|
||||
Leaving it since there are some references in refund that I need to look into
|
||||
*/
|
||||
.manual-box {
|
||||
border-radius: 5px;
|
||||
margin-left: calc(-40px + 10%);
|
||||
@ -8882,6 +8882,10 @@ strong {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.manual-box__address__value .copied-label {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.manual-box__address__wrapper {
|
||||
background: rgba(182, 182, 182, 0.13);
|
||||
border: 1px solid rgba(77, 77, 77, 0.07);
|
||||
@ -8903,6 +8907,7 @@ strong {
|
||||
font-size: 10.2px;
|
||||
color: #4A4A4A;
|
||||
}
|
||||
/* EOF REFACTOR */
|
||||
|
||||
.status-block {
|
||||
position: relative;
|
||||
@ -11286,3 +11291,128 @@ low-fee-timeline {
|
||||
.no-bounce * .status-icon__wrapper__outline {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
|
||||
/* checkout additions */
|
||||
.copyBox {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #E9E9E9;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.copySectionBox {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.copySectionBox label {
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
color: #515664;
|
||||
opacity: .5;
|
||||
letter-spacing: .6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.copySectionBox.bottomBorder {
|
||||
border-bottom: 1px solid #e9e9e9;
|
||||
}
|
||||
|
||||
.separatorGem {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
left: 50%;
|
||||
border-right: 1px solid #E9E9E9;
|
||||
border-bottom: 1px solid #E9E9E9;
|
||||
transform: rotateZ(45deg);
|
||||
margin-left: -5px;
|
||||
top: -5px;
|
||||
background: #329F80;
|
||||
transform-style: preserve-3d;
|
||||
backface-visibility: hidden;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
|
||||
.checkoutTextbox {
|
||||
width: 100%;
|
||||
border: 1px solid #e9e9e9;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
padding: 8px 10px;
|
||||
background: #f6f6f6;
|
||||
box-sizing: border-box;
|
||||
transition: .3s;
|
||||
font-size: 11px;
|
||||
color: #4a4a4a;
|
||||
cursor: copy;
|
||||
}
|
||||
|
||||
.inputWithIcon .checkoutTextbox {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.inputWithIcon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputWithIcon img {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 8px;
|
||||
color: #aaa;
|
||||
height: 16px;
|
||||
padding: 0px 6px;
|
||||
border-right: 1px solid #e9e9e9;
|
||||
}
|
||||
|
||||
.inputWithIcon.inputIconBg img {
|
||||
background-color: #aaa;
|
||||
color: #fff;
|
||||
padding: 9px 4px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.copyAmountText {
|
||||
color: #4A4A4A;
|
||||
font-size: 30px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 1px;
|
||||
margin-top: -10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-cursor {
|
||||
cursor: copy;
|
||||
}
|
||||
|
||||
.clipboardCopied {
|
||||
transition: opacity 1s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.copyLabelPopup {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
transition: opacity 1s ease;
|
||||
position: fixed;
|
||||
width: 130px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background: #4A4A4A;
|
||||
padding: 10px 40px;
|
||||
border-radius: 3px;
|
||||
letter-spacing: .5px;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
.copyLabelPopup.copied {
|
||||
pointer-events: auto;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease;
|
||||
}
|
||||
|
@ -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;
|
||||
@ -306,26 +306,39 @@ $(document).ready(function () {
|
||||
}
|
||||
}
|
||||
|
||||
// Manual Copy
|
||||
// Amount
|
||||
var copyAmount = new Clipboard('.manual-box__amount__value', {
|
||||
target: function () {
|
||||
var $el = $(".manual-box__amount__value");
|
||||
$el.removeClass("copy-cursor").addClass("copied");
|
||||
setTimeout(function () { $el.removeClass("copied").addClass("copy-cursor"); }, 500);
|
||||
return document.querySelector('.manual-box__amount__value span');
|
||||
// Clipboard Copy
|
||||
var copyAmount = new Clipboard('._copySpan', {
|
||||
target: function (trigger) {
|
||||
return copyElement(trigger, 0, 65).firstChild;
|
||||
}
|
||||
});
|
||||
// Address
|
||||
var copyAddress = new Clipboard('.manual-box__address__value', {
|
||||
target: function () {
|
||||
var $elm = $(".manual-box__address__value");
|
||||
$elm.removeClass("copy-cursor").addClass("copied");
|
||||
setTimeout(function () { $elm.removeClass("copied").addClass("copy-cursor"); }, 500);
|
||||
return document.querySelector('.manual-box__address__value .manual-box__address__wrapper .manual-box__address__wrapper__value');
|
||||
var copyAmount = new Clipboard('._copyInput', {
|
||||
target: function (trigger) {
|
||||
return copyElement(trigger, 4, 65).firstChild;
|
||||
}
|
||||
});
|
||||
|
||||
function copyElement(trigger, popupLeftModifier, popupTopModifier) {
|
||||
var elm = $(trigger);
|
||||
var position = elm.offset();
|
||||
position.top -= popupLeftModifier;
|
||||
position.left += (elm.width() / 2) - popupTopModifier;
|
||||
$(".copyLabelPopup").css(position).addClass("copied");
|
||||
|
||||
elm.removeClass("copy-cursor").addClass("clipboardCopied");
|
||||
setTimeout(clearSelection, 100);
|
||||
setTimeout(function () {
|
||||
elm.removeClass("clipboardCopied").addClass("copy-cursor");
|
||||
$(".copyLabelPopup").removeClass("copied");
|
||||
}, 1000);
|
||||
return trigger;
|
||||
}
|
||||
function clearSelection() {
|
||||
if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
||||
else if (document.selection) { document.selection.empty(); }
|
||||
}
|
||||
// EOF Copy
|
||||
|
||||
// Disable enter key
|
||||
$(document).keypress(
|
||||
function (event) {
|
||||
|
@ -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",
|
||||
|
@ -44,5 +44,8 @@ You can return to {{storeName}} if you would like to submit your payment again."
|
||||
"This invoice has been paid": "This invoice has been paid",
|
||||
// Invoice archived
|
||||
"This invoice has been archived": "This invoice has been archived",
|
||||
"Archived_Body": "Please contact the store for order information or assistance"
|
||||
"Archived_Body": "Please contact the store for order information or assistance",
|
||||
// Lightning
|
||||
"BOLT 11 Invoice": "BOLT 11 Invoice",
|
||||
"Peer Info": "Peer Info"
|
||||
};
|
||||
|
48
BTCPayServer/wwwroot/js/checkout/langs/is.js
Normal file
48
BTCPayServer/wwwroot/js/checkout/langs/is.js
Normal file
@ -0,0 +1,48 @@
|
||||
const locales_is = {
|
||||
nested: {
|
||||
lang: 'Tungumál'
|
||||
},
|
||||
"Awaiting Payment...": "Bíð eftir greiðslu...",
|
||||
"Pay with": "Borga með",
|
||||
"Contact and Refund Email": "Netfang",
|
||||
"Contact_Body": "Við munum hafa samband við þig á þessu netfangi ef það er vandamál með greiðsluna þína.",
|
||||
"Your email": "Þitt netfang",
|
||||
"Continue": "Áfram",
|
||||
"Please enter a valid email address": "Þú verður að nota gilt netfang",
|
||||
"Order Amount": "Upphæð",
|
||||
"Network Cost": "Auka gjöld",
|
||||
"Already Paid": "Nú þegar greitt",
|
||||
"Due": "Due",
|
||||
// Tabs
|
||||
"Scan": "Skanna",
|
||||
"Copy": "Afrita",
|
||||
"Conversion": "Umbreyting",
|
||||
// Scan tab
|
||||
"Open in wallet": "Opna með veski",
|
||||
// Copy tab
|
||||
"CompletePay_Body": "Til að klára greiðsluna skaltu senda {{btcDue}} {{cryptoCode}} á lykilinn fyrir neðan.",
|
||||
"Amount": "Magn",
|
||||
"Address": "Lykill",
|
||||
"Copied": "Afritað",
|
||||
// Conversion tab
|
||||
"ConversionTab_BodyTop": "Þú getur borgað {{btcDue}} {{cryptoCode}} með altcoins.",
|
||||
"ConversionTab_BodyDesc": "Þessi þjónusta er veitt af þriðja aðila. Mundu að við höfum ekki stjórn á því hvernig birgja vilja senda peningana þína. Reikningur verður aðeins merktur þegar móttekinir eru á {{cryptoCode}} Blockchain.",
|
||||
"Shapeshift_Button_Text": "Borga með Altcoins",
|
||||
"ConversionTab_Lightning": "Engir viðskiptaveitendur eru í boði fyrir Lightning Network greiðslur.",
|
||||
// Invoice expired
|
||||
"Invoice expiring soon...": "Innheimt rennur út fljótlega...",
|
||||
"Invoice expired": "Innheimt útrunnin",
|
||||
"What happened?": "Hvað gerðist?",
|
||||
"InvoiceExpired_Body_1": "Þessi innheimtun er útrunnin. Innheimtun er aðeins gild í {{maxTimeMinutes}} mínútur. \
|
||||
Þú getur farið aftur á {{storeName}} ef þú vilt reyna aftur.",
|
||||
"InvoiceExpired_Body_2": "Ef þú reyndir að senda greiðslu, þá hefur hún ekki verið samþykkt.",
|
||||
"InvoiceExpired_Body_3": "Ef viðskiptin eru ekki samþykkt af Bitcoin netinu verða fjármunirnir aðgengilegar aftur í veskinu þínu. Það fer eftir veskinu þínu og getur tekið 48-72 klukkustundir.",
|
||||
"Invoice ID": "Innheimtu ID",
|
||||
"Order ID": "Pöntun ID",
|
||||
"Return to StoreName": "Fara aftur á {{storeName}}",
|
||||
// Invoice paid
|
||||
"This invoice has been paid": "Þetta hefur verið greitt",
|
||||
// Invoice archived
|
||||
"This invoice has been archived": "Þessi innheimta hefur verið gerð ógild",
|
||||
"Archived_Body": "Vinsamlega hafðu samband fyrir upplýsingar eða aðstoð."
|
||||
};
|
Reference in New Issue
Block a user