Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
c707f47b11 | |||
585efa3ff5 | |||
07d0b98a23 | |||
c7c0f01010 | |||
cf6b17250a | |||
90503a490c | |||
ebdd53b99b | |||
51a5d2e812 | |||
2ad509d56a | |||
1a98bfba36 | |||
d05bb6c60e | |||
ed81b6a6aa | |||
264914588f | |||
05df43b426 | |||
0334a4e176 | |||
38dca425da | |||
82d4a79dd4 | |||
6725be8145 | |||
f5b693f01b | |||
f09f23e570 | |||
4f4d05b8cd | |||
0c5b5ff49c | |||
a815fad3f1 | |||
d8b1c7c10a | |||
02e1aea80c | |||
1892f7e0f4 | |||
b7b50349a7 | |||
02d227ee02 | |||
47f8938b89 | |||
4945a640a7 | |||
0136977359 | |||
0acd3e20b0 | |||
30bdfeee37 | |||
7ea665d884 | |||
073edcfb12 | |||
a645366a25 | |||
12aa0b7abd | |||
3f98a50410 | |||
24c8c076d5 | |||
37e6931d33 |
@ -1629,18 +1629,16 @@ donation:
|
||||
public void PosDataParser_ParsesCorrectly()
|
||||
{
|
||||
var testCases =
|
||||
new List<(string input, Dictionary<string, string> expectedOutput)>()
|
||||
new List<(string input, Dictionary<string, object> expectedOutput)>()
|
||||
{
|
||||
{ (null, new Dictionary<string, string>())},
|
||||
{("", new Dictionary<string, string>())},
|
||||
{("{}", new Dictionary<string, string>())},
|
||||
{("non-json-content", new Dictionary<string, string>(){ {string.Empty, "non-json-content"}})},
|
||||
{("[1,2,3]", new Dictionary<string, string>(){ {string.Empty, "[1,2,3]"}})},
|
||||
{("{ \"key\": \"value\"}", new Dictionary<string, string>(){ {"key", "value"}})},
|
||||
{("{ \"key\": true}", new Dictionary<string, string>(){ {"key", "True"}})},
|
||||
{("{ \"key\": \"value\", \"key2\": [\"value\", \"value2\"]}",
|
||||
new Dictionary<string, string>(){ {"key", "value"}, {"key2", "value,value2"}})},
|
||||
{("{ invalidjson file here}", new Dictionary<string, string>(){ {String.Empty, "{ invalidjson file here}"}})}
|
||||
{ (null, new Dictionary<string, object>())},
|
||||
{("", new Dictionary<string, object>())},
|
||||
{("{}", new Dictionary<string, object>())},
|
||||
{("non-json-content", new Dictionary<string, object>(){ {string.Empty, "non-json-content"}})},
|
||||
{("[1,2,3]", new Dictionary<string, object>(){ {string.Empty, "[1,2,3]"}})},
|
||||
{("{ \"key\": \"value\"}", new Dictionary<string, object>(){ {"key", "value"}})},
|
||||
{("{ \"key\": true}", new Dictionary<string, object>(){ {"key", "True"}})},
|
||||
{("{ invalidjson file here}", new Dictionary<string, object>(){ {String.Empty, "{ invalidjson file here}"}})}
|
||||
};
|
||||
|
||||
testCases.ForEach(tuple =>
|
||||
@ -1663,18 +1661,16 @@ donation:
|
||||
var controller = tester.PayTester.GetController<InvoiceController>(null);
|
||||
|
||||
var testCases =
|
||||
new List<(string input, Dictionary<string, string> expectedOutput)>()
|
||||
new List<(string input, Dictionary<string, object> expectedOutput)>()
|
||||
{
|
||||
{ (null, new Dictionary<string, string>())},
|
||||
{("", new Dictionary<string, string>())},
|
||||
{("{}", new Dictionary<string, string>())},
|
||||
{("non-json-content", new Dictionary<string, string>(){ {string.Empty, "non-json-content"}})},
|
||||
{("[1,2,3]", new Dictionary<string, string>(){ {string.Empty, "[1,2,3]"}})},
|
||||
{("{ \"key\": \"value\"}", new Dictionary<string, string>(){ {"key", "value"}})},
|
||||
{("{ \"key\": true}", new Dictionary<string, string>(){ {"key", "True"}})},
|
||||
{("{ \"key\": \"value\", \"key2\": [\"value\", \"value2\"]}",
|
||||
new Dictionary<string, string>(){ {"key", "value"}, {"key2", "value,value2"}})},
|
||||
{("{ invalidjson file here}", new Dictionary<string, string>(){ {String.Empty, "{ invalidjson file here}"}})}
|
||||
{ (null, new Dictionary<string, object>())},
|
||||
{("", new Dictionary<string, object>())},
|
||||
{("{}", new Dictionary<string, object>())},
|
||||
{("non-json-content", new Dictionary<string, object>(){ {string.Empty, "non-json-content"}})},
|
||||
{("[1,2,3]", new Dictionary<string, object>(){ {string.Empty, "[1,2,3]"}})},
|
||||
{("{ \"key\": \"value\"}", new Dictionary<string, object>(){ {"key", "value"}})},
|
||||
{("{ \"key\": true}", new Dictionary<string, object>(){ {"key", "True"}})},
|
||||
{("{ invalidjson file here}", new Dictionary<string, object>(){ {String.Empty, "{ invalidjson file here}"}})}
|
||||
};
|
||||
|
||||
var tasks = new List<Task>();
|
||||
@ -1988,6 +1984,7 @@ donation:
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5000.0m,
|
||||
TaxIncluded = 1000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
@ -2017,6 +2014,8 @@ donation:
|
||||
});
|
||||
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal(1000.0m, invoice.TaxIncluded);
|
||||
Assert.Equal(5000.0m, invoice.Price);
|
||||
Assert.Equal(Money.Coins(0), invoice.BtcPaid);
|
||||
Assert.Equal("new", invoice.Status);
|
||||
Assert.False((bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
@ -2139,19 +2138,20 @@ donation:
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CheckQuadrigacxRateProvider()
|
||||
{
|
||||
var quadri = new QuadrigacxRateProvider();
|
||||
var rates = quadri.GetRatesAsync().GetAwaiter().GetResult();
|
||||
Assert.NotEmpty(rates);
|
||||
Assert.NotEqual(0.0m, rates.First().BidAsk.Bid);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Bid);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Bid);
|
||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Bid);
|
||||
Assert.Null(rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_USD")));
|
||||
}
|
||||
//[Fact]
|
||||
//[Trait("Integration", "Integration")]
|
||||
// 29 january, the exchange is down
|
||||
//public void CheckQuadrigacxRateProvider()
|
||||
//{
|
||||
// var quadri = new QuadrigacxRateProvider();
|
||||
// var rates = quadri.GetRatesAsync().GetAwaiter().GetResult();
|
||||
// Assert.NotEmpty(rates);
|
||||
// Assert.NotEqual(0.0m, rates.First().BidAsk.Bid);
|
||||
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Bid);
|
||||
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Bid);
|
||||
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Bid);
|
||||
// Assert.Null(rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_USD")));
|
||||
//}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
@ -2165,6 +2165,9 @@ donation:
|
||||
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync(), Fetcher: (BackgroundFetcherRateProvider)p.Value))
|
||||
.ToList())
|
||||
{
|
||||
Logs.Tester.LogInformation($"Testing {result.ExpectedName}");
|
||||
if (result.ExpectedName == "quadrigacx")
|
||||
continue; // 29 january, the exchange is down
|
||||
result.Fetcher.InvalidateCache();
|
||||
var exchangeRates = result.ResultAsync.Result;
|
||||
result.Fetcher.InvalidateCache();
|
||||
@ -2313,6 +2316,42 @@ donation:
|
||||
Assert.Throws<InvalidOperationException>(() => fetch.GetRatesAsync().GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CheckParseStatusMessageModel()
|
||||
{
|
||||
var legacyStatus = "Error: some bad shit happened";
|
||||
var parsed = new StatusMessageModel(legacyStatus);
|
||||
Assert.Equal(legacyStatus, parsed.Message);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Error, parsed.Severity);
|
||||
|
||||
var legacyStatus2 = "Some normal shit happened";
|
||||
parsed = new StatusMessageModel(legacyStatus2);
|
||||
Assert.Equal(legacyStatus2, parsed.Message);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, parsed.Severity);
|
||||
|
||||
var newStatus = new StatusMessageModel()
|
||||
{
|
||||
Html = "<a href='xxx'>something new</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Info
|
||||
};
|
||||
parsed = new StatusMessageModel(newStatus.ToString());
|
||||
Assert.Null(parsed.Message);
|
||||
Assert.Equal(newStatus.Html, parsed.Html);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Info, parsed.Severity);
|
||||
|
||||
var newStatus2 = new StatusMessageModel()
|
||||
{
|
||||
Message = "something new",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
};
|
||||
parsed = new StatusMessageModel(newStatus2.ToString());
|
||||
Assert.Null(parsed.Html);
|
||||
Assert.Equal(newStatus2.Message, parsed.Message);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, parsed.Severity);
|
||||
|
||||
}
|
||||
|
||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||
{
|
||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest).ScriptPubKey.Hash.ToString();
|
||||
|
35
BTCPayServer/BTCPayNetworkProvider.Bitcoinplus.cs
Normal file
35
BTCPayServer/BTCPayNetworkProvider.Bitcoinplus.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitBitcoinplus()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("XBC");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Bitcoinplus",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoinplus",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"XBC_X = XBC_BTC * BTC_X",
|
||||
"XBC_BTC = cryptopia(XBC_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/bitcoinplus.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -52,11 +52,13 @@ namespace BTCPayServer
|
||||
InitBitcoinGold();
|
||||
InitMonacoin();
|
||||
InitDash();
|
||||
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
|
||||
//InitPolis();
|
||||
InitFeathercoin();
|
||||
InitGroestlcoin();
|
||||
InitViacoin();
|
||||
|
||||
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
|
||||
//InitPolis();
|
||||
//InitBitcoinplus();
|
||||
//InitUfo();
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<Version>1.0.3.45</Version>
|
||||
<Version>1.0.3.54</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@ -33,7 +33,7 @@
|
||||
<EmbeddedResource Include="Currencies.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.4" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.5" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="2.7.385" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.5.3" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="4.0.199" />
|
||||
@ -45,8 +45,8 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.77" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.30" />
|
||||
<PackageReference Include="NBitcoin" Version="4.1.1.78" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.31" />
|
||||
<PackageReference Include="DBreeze" Version="1.92.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
|
@ -2,6 +2,7 @@
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
@ -14,6 +15,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)]
|
||||
[BitpayAPIConstraint(true)]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public class AccessTokenController : Controller
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
|
@ -85,6 +85,7 @@ namespace BTCPayServer.Controllers
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
var vm = new UpdatePointOfSaleViewModel()
|
||||
{
|
||||
Id = appId,
|
||||
Title = settings.Title,
|
||||
EnableShoppingCart = settings.EnableShoppingCart,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
|
@ -73,7 +73,12 @@ namespace BTCPayServer.Controllers
|
||||
var stores = await _AppsHelper.GetOwnedStores(GetUserId());
|
||||
if (stores.Length == 0)
|
||||
{
|
||||
StatusMessage = "Error: You must have created at least one store";
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Html =
|
||||
$"Error: You must have created at least one store. <a href='{(Url.Action("CreateStore", "UserStores"))}'>Create store</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
}.ToString();
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
}
|
||||
var vm = new CreateAppViewModel();
|
||||
@ -88,7 +93,12 @@ namespace BTCPayServer.Controllers
|
||||
var stores = await _AppsHelper.GetOwnedStores(GetUserId());
|
||||
if (stores.Length == 0)
|
||||
{
|
||||
StatusMessage = "Error: You must own at least one store";
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Html =
|
||||
$"Error: You must have created at least one store. <a href='{(Url.Action("CreateStore", "UserStores"))}'>Create store</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
}.ToString();
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
}
|
||||
var selectedStore = vm.SelectedStore;
|
||||
|
@ -25,6 +25,7 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using static BTCPayServer.Controllers.AppsController;
|
||||
|
||||
@ -49,7 +50,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("/apps/{appId}/pos")]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId)
|
||||
{
|
||||
var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale);
|
||||
@ -90,7 +91,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("/apps/{appId}/crowdfund")]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
public async Task<IActionResult> ViewCrowdfund(string appId, string statusMessage)
|
||||
|
||||
{
|
||||
@ -119,7 +120,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("/apps/{appId}/crowdfund")]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
[IgnoreAntiforgeryToken]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request)
|
||||
@ -155,10 +156,11 @@ namespace BTCPayServer.Controllers
|
||||
var store = await _AppsHelper.GetStore(app);
|
||||
var title = settings.Title;
|
||||
var price = request.Amount;
|
||||
ViewPointOfSaleViewModel.Item choice = null;
|
||||
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
||||
{
|
||||
var choices = _AppsHelper.Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||
var choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey);
|
||||
choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey);
|
||||
if (choice == null)
|
||||
return NotFound("Incorrect option provided");
|
||||
title = choice.Title;
|
||||
@ -174,35 +176,44 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new Invoice()
|
||||
try
|
||||
{
|
||||
OrderId = $"{CrowdfundHubStreamer.CrowdfundInvoiceOrderIdPrefix}{appId}",
|
||||
Currency = settings.TargetCurrency,
|
||||
ItemCode = request.ChoiceKey ?? string.Empty,
|
||||
ItemDesc = title,
|
||||
BuyerEmail = request.Email,
|
||||
Price = price,
|
||||
NotificationURL = settings.NotificationUrl,
|
||||
FullNotifications = true,
|
||||
ExtendedNotifications = true,
|
||||
RedirectURL = request.RedirectUrl ?? Request.GetDisplayUrl(),
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new Invoice()
|
||||
{
|
||||
OrderId = $"{CrowdfundHubStreamer.CrowdfundInvoiceOrderIdPrefix}{appId}",
|
||||
Currency = settings.TargetCurrency,
|
||||
ItemCode = request.ChoiceKey ?? string.Empty,
|
||||
ItemDesc = title,
|
||||
BuyerEmail = request.Email,
|
||||
Price = price,
|
||||
NotificationURL = settings.NotificationUrl,
|
||||
FullNotifications = true,
|
||||
ExtendedNotifications = true,
|
||||
RedirectURL = request.RedirectUrl ?? Request.GetDisplayUrl(),
|
||||
|
||||
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||
if (request.RedirectToCheckout)
|
||||
{
|
||||
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice",
|
||||
new {invoiceId = invoice.Data.Id});
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||
if (request.RedirectToCheckout)
|
||||
{
|
||||
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice",
|
||||
new {invoiceId = invoice.Data.Id});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Ok(invoice.Data.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (BitpayHttpException e)
|
||||
{
|
||||
return Ok(invoice.Data.Id);
|
||||
return BadRequest(e.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("/apps/{appId}/pos")]
|
||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
[IgnoreAntiforgeryToken]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId,
|
||||
@ -211,7 +222,8 @@ namespace BTCPayServer.Controllers
|
||||
string orderId,
|
||||
string notificationUrl,
|
||||
string redirectUrl,
|
||||
string choiceKey)
|
||||
string choiceKey,
|
||||
string posData = null)
|
||||
{
|
||||
var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale);
|
||||
if (string.IsNullOrEmpty(choiceKey) && amount <= 0)
|
||||
@ -227,10 +239,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
string title = null;
|
||||
var price = 0.0m;
|
||||
ViewPointOfSaleViewModel.Item choice = null;
|
||||
if (!string.IsNullOrEmpty(choiceKey))
|
||||
{
|
||||
var choices = _AppsHelper.Parse(settings.Template, settings.Currency);
|
||||
var choice = choices.FirstOrDefault(c => c.Id == choiceKey);
|
||||
choice = choices.FirstOrDefault(c => c.Id == choiceKey);
|
||||
if (choice == null)
|
||||
return NotFound();
|
||||
title = choice.Title;
|
||||
@ -249,7 +262,7 @@ namespace BTCPayServer.Controllers
|
||||
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
|
||||
var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice()
|
||||
{
|
||||
ItemCode = choiceKey ?? string.Empty,
|
||||
ItemCode = choice?.Id,
|
||||
ItemDesc = title,
|
||||
Currency = settings.Currency,
|
||||
Price = price,
|
||||
@ -258,6 +271,7 @@ namespace BTCPayServer.Controllers
|
||||
NotificationURL = notificationUrl,
|
||||
RedirectURL = redirectUrl ?? Request.GetDisplayUrl(),
|
||||
FullNotifications = true,
|
||||
PosData = string.IsNullOrEmpty(posData) ? null : posData
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id });
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ using NBitpayClient;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[EnableCors("BitpayAPI")]
|
||||
[BitpayAPIConstraint]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
[Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = Policies.BitpayAuthentication)]
|
||||
public class InvoiceControllerAPI : Controller
|
||||
{
|
||||
|
@ -21,6 +21,7 @@ using BTCPayServer.Services.Invoices.Export;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore.Internal;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
@ -64,6 +65,7 @@ namespace BTCPayServer.Controllers
|
||||
OrderId = invoice.OrderId,
|
||||
BuyerInformation = invoice.BuyerInformation,
|
||||
Fiat = _CurrencyNameTable.DisplayFormatCurrency(dto.Price, dto.Currency),
|
||||
TaxIncluded = _CurrencyNameTable.DisplayFormatCurrency(invoice.ProductInformation.TaxIncluded, dto.Currency),
|
||||
NotificationEmail = invoice.NotificationEmail,
|
||||
NotificationUrl = invoice.NotificationURL,
|
||||
RedirectUrl = invoice.RedirectURL,
|
||||
@ -80,9 +82,9 @@ namespace BTCPayServer.Controllers
|
||||
var paymentMethodId = data.GetId();
|
||||
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
|
||||
cryptoPayment.PaymentMethod = ToString(paymentMethodId);
|
||||
cryptoPayment.Due = $"{accounting.Due} {paymentMethodId.CryptoCode}";
|
||||
cryptoPayment.Paid = $"{accounting.CryptoPaid} {paymentMethodId.CryptoCode}";
|
||||
cryptoPayment.Overpaid = $"{accounting.OverpaidHelper} {paymentMethodId.CryptoCode}";
|
||||
cryptoPayment.Due = _CurrencyNameTable.DisplayFormatCurrency(accounting.Due.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
||||
cryptoPayment.Paid = _CurrencyNameTable.DisplayFormatCurrency(accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
||||
cryptoPayment.Overpaid = _CurrencyNameTable.DisplayFormatCurrency(accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
||||
|
||||
var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
||||
if (onchainMethod != null)
|
||||
@ -188,7 +190,7 @@ namespace BTCPayServer.Controllers
|
||||
id = invoiceId;
|
||||
////
|
||||
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId);
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId));
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
@ -211,31 +213,29 @@ namespace BTCPayServer.Controllers
|
||||
return View(nameof(Checkout), model);
|
||||
}
|
||||
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, string paymentMethodIdStr)
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
bool isDefaultCrypto = false;
|
||||
if (paymentMethodIdStr == null)
|
||||
bool isDefaultPaymentId = false;
|
||||
if (paymentMethodId == null)
|
||||
{
|
||||
paymentMethodIdStr = store.GetDefaultCrypto(_NetworkProvider);
|
||||
isDefaultCrypto = true;
|
||||
paymentMethodId = store.GetDefaultPaymentId(_NetworkProvider);
|
||||
isDefaultPaymentId = true;
|
||||
}
|
||||
var paymentMethodId = PaymentMethodId.Parse(paymentMethodIdStr);
|
||||
var network = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
|
||||
if (network == null && isDefaultCrypto)
|
||||
if (network == null && isDefaultPaymentId)
|
||||
{
|
||||
network = _NetworkProvider.GetAll().FirstOrDefault();
|
||||
paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
paymentMethodIdStr = paymentMethodId.ToString();
|
||||
}
|
||||
if (invoice == null || network == null)
|
||||
return null;
|
||||
if (!invoice.Support(paymentMethodId))
|
||||
{
|
||||
if (!isDefaultCrypto)
|
||||
if (!isDefaultPaymentId)
|
||||
return null;
|
||||
var paymentMethodTemp = invoice.GetPaymentMethods(_NetworkProvider)
|
||||
.Where(c => paymentMethodId.CryptoCode == c.GetId().CryptoCode)
|
||||
@ -244,7 +244,6 @@ namespace BTCPayServer.Controllers
|
||||
paymentMethodTemp = invoice.GetPaymentMethods(_NetworkProvider).First();
|
||||
network = paymentMethodTemp.Network;
|
||||
paymentMethodId = paymentMethodTemp.GetId();
|
||||
paymentMethodIdStr = paymentMethodId.ToString();
|
||||
}
|
||||
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId, _NetworkProvider);
|
||||
@ -278,7 +277,6 @@ namespace BTCPayServer.Controllers
|
||||
PaymentMethodName = GetDisplayName(paymentMethodId, network),
|
||||
CryptoImage = GetImage(paymentMethodId, network),
|
||||
IsLightning = paymentMethodId.PaymentType == PaymentTypes.LightningLike,
|
||||
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
||||
OrderId = invoice.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
DefaultLang = storeBlob.DefaultLang ?? "en",
|
||||
@ -369,9 +367,12 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/status")]
|
||||
[Route("i/{invoiceId}/{paymentMethodId}/status")]
|
||||
[Route("invoice/{invoiceId}/status")]
|
||||
[Route("invoice/{invoiceId}/{paymentMethodId}/status")]
|
||||
[Route("invoice/status")]
|
||||
public async Task<IActionResult> GetStatus(string invoiceId, string paymentMethodId = null)
|
||||
{
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId);
|
||||
var model = await GetInvoiceModel(invoiceId, paymentMethodId == null ? null : PaymentMethodId.Parse(paymentMethodId));
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
return Json(model);
|
||||
@ -379,6 +380,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/status/ws")]
|
||||
[Route("i/{invoiceId}/{paymentMethodId}/status/ws")]
|
||||
[Route("invoice/{invoiceId}/status/ws")]
|
||||
[Route("invoice/{invoiceId}/{paymentMethodId}/status")]
|
||||
[Route("invoice/status/ws")]
|
||||
public async Task<IActionResult> GetStatusWebSocket(string invoiceId)
|
||||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
@ -424,6 +429,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}/UpdateCustomer")]
|
||||
[Route("invoice/UpdateCustomer")]
|
||||
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
@ -452,6 +458,7 @@ namespace BTCPayServer.Controllers
|
||||
invoiceQuery.Count = count;
|
||||
invoiceQuery.Skip = skip;
|
||||
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
||||
|
||||
foreach (var invoice in list)
|
||||
{
|
||||
var state = invoice.GetInvoiceState();
|
||||
@ -463,7 +470,7 @@ namespace BTCPayServer.Controllers
|
||||
InvoiceId = invoice.Id,
|
||||
OrderId = invoice.OrderId ?? string.Empty,
|
||||
RedirectUrl = invoice.RedirectURL ?? string.Empty,
|
||||
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}",
|
||||
AmountCurrency = _CurrencyNameTable.DisplayFormatCurrency(invoice.ProductInformation.Price, invoice.ProductInformation.Currency),
|
||||
CanMarkInvalid = state.CanMarkInvalid(),
|
||||
CanMarkComplete = state.CanMarkComplete()
|
||||
});
|
||||
@ -679,9 +686,9 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
public class PosDataParser
|
||||
{
|
||||
public static Dictionary<string, string> ParsePosData(string posData)
|
||||
public static Dictionary<string, object> ParsePosData(string posData)
|
||||
{
|
||||
var result = new Dictionary<string,string>();
|
||||
var result = new Dictionary<string,object>();
|
||||
if (string.IsNullOrEmpty(posData))
|
||||
{
|
||||
return result;
|
||||
@ -689,7 +696,6 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
var jObject =JObject.Parse(posData);
|
||||
foreach (var item in jObject)
|
||||
{
|
||||
@ -697,7 +703,14 @@ namespace BTCPayServer.Controllers
|
||||
switch (item.Value.Type)
|
||||
{
|
||||
case JTokenType.Array:
|
||||
result.Add(item.Key, string.Join(',', item.Value.AsEnumerable()));
|
||||
var items = item.Value.AsEnumerable().ToList();
|
||||
for (var i = 0; i < items.Count(); i++)
|
||||
{
|
||||
result.Add($"{item.Key}[{i}]", ParsePosData(items[i].ToString()));
|
||||
}
|
||||
break;
|
||||
case JTokenType.Object:
|
||||
result.Add(item.Key, ParsePosData(item.Value.ToString()));
|
||||
break;
|
||||
default:
|
||||
result.Add(item.Key, item.Value.ToString());
|
||||
|
@ -99,8 +99,12 @@ namespace BTCPayServer.Controllers
|
||||
if (currencyInfo != null)
|
||||
{
|
||||
invoice.Price = Math.Round(invoice.Price, currencyInfo.CurrencyDecimalDigits);
|
||||
invoice.TaxIncluded = Math.Round(invoice.TaxIncluded, currencyInfo.CurrencyDecimalDigits);
|
||||
}
|
||||
invoice.Price = Math.Max(0.0m, invoice.Price);
|
||||
invoice.TaxIncluded = Math.Max(0.0m, invoice.TaxIncluded);
|
||||
invoice.TaxIncluded = Math.Min(invoice.TaxIncluded, invoice.Price);
|
||||
|
||||
entity.ProductInformation = Map<Invoice, ProductInformation>(invoice);
|
||||
|
||||
|
||||
|
@ -12,11 +12,13 @@ using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using BTCPayServer.Authentication;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)]
|
||||
[AllowAnonymous]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public class RateController : Controller
|
||||
{
|
||||
RateFetcher _RateProviderFactory;
|
||||
@ -139,9 +141,9 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var supportedMethods = store.GetSupportedPaymentMethods(_NetworkProvider);
|
||||
var currencyCodes = supportedMethods.Select(method => method.PaymentId.CryptoCode).Distinct();
|
||||
var defaultCrypto = store.GetDefaultCrypto(_NetworkProvider);
|
||||
var defaultPaymentId = store.GetDefaultPaymentId(_NetworkProvider);
|
||||
|
||||
currencyPairs = BuildCurrencyPairs(currencyCodes, defaultCrypto);
|
||||
currencyPairs = BuildCurrencyPairs(currencyCodes, defaultPaymentId.CryptoCode);
|
||||
|
||||
if (string.IsNullOrEmpty(currencyPairs))
|
||||
{
|
||||
|
@ -208,8 +208,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
builder.Scheme = this.Request.Scheme;
|
||||
builder.Host = vm.DNSDomain;
|
||||
var addresses1 = Dns.GetHostAddressesAsync(this.Request.Host.Host);
|
||||
var addresses2 = Dns.GetHostAddressesAsync(vm.DNSDomain);
|
||||
var addresses1 = GetAddressAsync(this.Request.Host.Host);
|
||||
var addresses2 = GetAddressAsync(vm.DNSDomain);
|
||||
await Task.WhenAll(addresses1, addresses2);
|
||||
|
||||
var addressesSet = addresses1.GetAwaiter().GetResult().Select(c => c.ToString()).ToHashSet();
|
||||
@ -253,6 +253,13 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(Maintenance));
|
||||
}
|
||||
|
||||
private Task<IPAddress[]> GetAddressAsync(string domainOrIP)
|
||||
{
|
||||
if (IPAddress.TryParse(domainOrIP, out var ip))
|
||||
return Task.FromResult(new[] { ip });
|
||||
return Dns.GetHostAddressesAsync(domainOrIP);
|
||||
}
|
||||
|
||||
public static string RunId = Encoders.Hex.EncodeData(NBitcoin.RandomUtils.GetBytes(32));
|
||||
[HttpGet]
|
||||
[Route("runid")]
|
||||
|
@ -331,7 +331,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var storeBlob = StoreData.GetStoreBlob();
|
||||
var vm = new CheckoutExperienceViewModel();
|
||||
vm.SetCryptoCurrencies(_ExplorerProvider, StoreData.GetDefaultCrypto(_NetworkProvider));
|
||||
SetCryptoCurrencies(vm, StoreData);
|
||||
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
||||
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
|
||||
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
|
||||
@ -341,6 +341,23 @@ namespace BTCPayServer.Controllers
|
||||
vm.HtmlTitle = storeBlob.HtmlTitle;
|
||||
return View(vm);
|
||||
}
|
||||
void SetCryptoCurrencies(CheckoutExperienceViewModel vm, Data.StoreData storeData)
|
||||
{
|
||||
var choices = storeData.GetEnabledPaymentIds(_NetworkProvider)
|
||||
.Select(o => new CheckoutExperienceViewModel.Format() { Name = GetDisplayName(o), Value = o.ToString(), PaymentId = o }).ToArray();
|
||||
|
||||
var defaultPaymentId = storeData.GetDefaultPaymentId(_NetworkProvider);
|
||||
var chosen = choices.FirstOrDefault(c => c.PaymentId == defaultPaymentId);
|
||||
vm.CryptoCurrencies = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen?.Value);
|
||||
vm.DefaultPaymentMethod = chosen?.Value;
|
||||
}
|
||||
|
||||
private string GetDisplayName(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var display = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode)?.DisplayName ?? paymentMethodId.CryptoCode;
|
||||
return paymentMethodId.PaymentType == PaymentTypes.BTCLike ?
|
||||
display : $"{display} (Lightning)";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/checkout")]
|
||||
@ -365,12 +382,13 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
bool needUpdate = false;
|
||||
var blob = StoreData.GetStoreBlob();
|
||||
if (StoreData.GetDefaultCrypto(_NetworkProvider) != model.DefaultCryptoCurrency)
|
||||
var defaultPaymentMethodId = model.DefaultPaymentMethod == null ? null : PaymentMethodId.Parse(model.DefaultPaymentMethod);
|
||||
if (StoreData.GetDefaultPaymentId(_NetworkProvider) != defaultPaymentMethodId)
|
||||
{
|
||||
needUpdate = true;
|
||||
StoreData.SetDefaultCrypto(model.DefaultCryptoCurrency);
|
||||
StoreData.SetDefaultPaymentId(defaultPaymentMethodId);
|
||||
}
|
||||
model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency);
|
||||
SetCryptoCurrencies(model, StoreData);
|
||||
model.SetLanguages(_LangService, model.DefaultLang);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
|
@ -56,7 +56,6 @@ namespace BTCPayServer.Data
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public IEnumerable<ISupportedPaymentMethod> GetSupportedPaymentMethods(BTCPayNetworkProvider networks)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
@ -195,7 +194,7 @@ namespace BTCPayServer.Data
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Obsolete("Use GetDefaultCrypto instead")]
|
||||
[Obsolete("Use GetDefaultPaymentId instead")]
|
||||
public string DefaultCrypto { get; set; }
|
||||
public List<PairedSINData> PairedSINs { get; set; }
|
||||
public IEnumerable<APIKeyData> APIKeys { get; set; }
|
||||
@ -204,13 +203,32 @@ namespace BTCPayServer.Data
|
||||
public List<Claim> AdditionalClaims { get; set; } = new List<Claim>();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
public string GetDefaultCrypto(BTCPayNetworkProvider networkProvider = null)
|
||||
public PaymentMethodId GetDefaultPaymentId(BTCPayNetworkProvider networks)
|
||||
{
|
||||
return DefaultCrypto ?? (networkProvider == null ? "BTC" : GetSupportedPaymentMethods(networkProvider).Select(p => p.PaymentId.CryptoCode).FirstOrDefault() ?? "BTC");
|
||||
PaymentMethodId[] paymentMethodIds = GetEnabledPaymentIds(networks);
|
||||
|
||||
var defaultPaymentId = string.IsNullOrEmpty(DefaultCrypto) ? null : PaymentMethodId.Parse(DefaultCrypto);
|
||||
var chosen = paymentMethodIds.FirstOrDefault(f => f == defaultPaymentId) ??
|
||||
paymentMethodIds.FirstOrDefault(f => f.CryptoCode == defaultPaymentId?.CryptoCode) ??
|
||||
paymentMethodIds.FirstOrDefault();
|
||||
return chosen;
|
||||
}
|
||||
public void SetDefaultCrypto(string defaultCryptoCurrency)
|
||||
|
||||
public PaymentMethodId[] GetEnabledPaymentIds(BTCPayNetworkProvider networks)
|
||||
{
|
||||
DefaultCrypto = defaultCryptoCurrency;
|
||||
var excludeFilter = GetStoreBlob().GetExcludedPaymentMethods();
|
||||
var paymentMethodIds = GetSupportedPaymentMethods(networks).Select(p => p.PaymentId)
|
||||
.Where(a => !excludeFilter.Match(a))
|
||||
.OrderByDescending(a => a.CryptoCode == "BTC")
|
||||
.ThenBy(a => a.CryptoCode)
|
||||
.ThenBy(a => a.PaymentType == PaymentTypes.LightningLike ? 1 : 0)
|
||||
.ToArray();
|
||||
return paymentMethodIds;
|
||||
}
|
||||
|
||||
public void SetDefaultPaymentId(PaymentMethodId defaultPaymentId)
|
||||
{
|
||||
DefaultCrypto = defaultPaymentId.ToString();
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
|
@ -12,13 +12,32 @@ namespace BTCPayServer.Filters
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public string Value
|
||||
|
||||
public XFrameOptionsAttribute(XFrameOptions type, string allowFrom = null)
|
||||
{
|
||||
get; set;
|
||||
switch (type)
|
||||
{
|
||||
case XFrameOptions.Deny:
|
||||
Value = "deny";
|
||||
break;
|
||||
case XFrameOptions.SameOrigin:
|
||||
Value = "deny";
|
||||
break;
|
||||
case XFrameOptions.AllowFrom:
|
||||
Value = $"allow-from {allowFrom}";
|
||||
break;
|
||||
case XFrameOptions.AllowAll:
|
||||
Value = "allow-all";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
@ -28,5 +47,13 @@ namespace BTCPayServer.Filters
|
||||
context.HttpContext.Response.SetHeaderOnStarting("X-Frame-Options", Value);
|
||||
}
|
||||
}
|
||||
|
||||
public enum XFrameOptions
|
||||
{
|
||||
Deny,
|
||||
SameOrigin,
|
||||
AllowFrom,
|
||||
AllowAll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,13 +33,10 @@ namespace BTCPayServer.HostedServices
|
||||
get; set;
|
||||
}
|
||||
|
||||
public InvoiceEntity Invoice
|
||||
public InvoicePaymentNotificationEventWrapper Notification
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int? EventCode { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
IBackgroundJobClient _JobClient;
|
||||
@ -63,22 +60,70 @@ namespace BTCPayServer.HostedServices
|
||||
_EmailSenderFactory = emailSenderFactory;
|
||||
}
|
||||
|
||||
void Notify(InvoiceEntity invoice, int? eventCode = null, string name = null)
|
||||
void Notify(InvoiceEntity invoice, InvoiceEvent invoiceEvent, bool extendedNotification)
|
||||
{
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var notification = new InvoicePaymentNotificationEventWrapper()
|
||||
{
|
||||
Data = new InvoicePaymentNotification()
|
||||
{
|
||||
Id = dto.Id,
|
||||
Currency = dto.Currency,
|
||||
CurrentTime = dto.CurrentTime,
|
||||
ExceptionStatus = dto.ExceptionStatus,
|
||||
ExpirationTime = dto.ExpirationTime,
|
||||
InvoiceTime = dto.InvoiceTime,
|
||||
PosData = dto.PosData,
|
||||
Price = dto.Price,
|
||||
Status = dto.Status,
|
||||
BuyerFields = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", invoice.RefundMail) },
|
||||
PaymentSubtotals = dto.PaymentSubtotals,
|
||||
PaymentTotals = dto.PaymentTotals,
|
||||
AmountPaid = dto.AmountPaid,
|
||||
ExchangeRates = dto.ExchangeRates,
|
||||
},
|
||||
Event = new InvoicePaymentNotificationEvent()
|
||||
{
|
||||
Code = invoiceEvent.EventCode,
|
||||
Name = invoiceEvent.Name
|
||||
},
|
||||
ExtendedNotification = extendedNotification,
|
||||
NotificationURL = invoice.NotificationURL
|
||||
};
|
||||
|
||||
// For lightning network payments, paid, confirmed and completed come all at once.
|
||||
// So despite the event is "paid" or "confirmed" the Status of the invoice is technically complete
|
||||
// This confuse loggers who think their endpoint get duplicated events
|
||||
// So here, we just override the status expressed by the notification
|
||||
if (invoiceEvent.Name == InvoiceEvent.Confirmed)
|
||||
{
|
||||
notification.Data.Status = InvoiceState.ToString(InvoiceStatus.Confirmed);
|
||||
}
|
||||
if (invoiceEvent.Name == InvoiceEvent.PaidInFull)
|
||||
{
|
||||
notification.Data.Status = InvoiceState.ToString(InvoiceStatus.Paid);
|
||||
}
|
||||
//////////////////
|
||||
|
||||
// We keep backward compatibility with bitpay by passing BTC info to the notification
|
||||
// we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
|
||||
var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetpaymentMethodId() == new PaymentMethodId("BTC", Payments.PaymentTypes.BTCLike));
|
||||
if (btcCryptoInfo != null)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
notification.Data.Rate = dto.Rate;
|
||||
notification.Data.Url = dto.Url;
|
||||
notification.Data.BTCDue = dto.BTCDue;
|
||||
notification.Data.BTCPaid = dto.BTCPaid;
|
||||
notification.Data.BTCPrice = dto.BTCPrice;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
|
||||
if (!String.IsNullOrEmpty(invoice.NotificationEmail))
|
||||
{
|
||||
// just extracting most important data for email body, merchant should query API back for full invoice based on Invoice.Id
|
||||
var ipn = new
|
||||
{
|
||||
invoice.Id,
|
||||
invoice.Status,
|
||||
invoice.StoreId
|
||||
};
|
||||
// TODO: Consider adding info on ItemDesc and payment info (amount)
|
||||
|
||||
var emailBody = NBitcoin.JsonConverters.Serializer.ToString(ipn);
|
||||
var emailBody = NBitcoin.JsonConverters.Serializer.ToString(notification);
|
||||
|
||||
_EmailSenderFactory.GetEmailSender(invoice.StoreId).SendEmail(
|
||||
invoice.NotificationEmail,
|
||||
@ -86,9 +131,9 @@ namespace BTCPayServer.HostedServices
|
||||
emailBody);
|
||||
|
||||
}
|
||||
if (string.IsNullOrEmpty(invoice.NotificationURL))
|
||||
if (string.IsNullOrEmpty(invoice.NotificationURL) || !Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute))
|
||||
return;
|
||||
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice, EventCode = eventCode, Message = name });
|
||||
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Notification = notification });
|
||||
if (!string.IsNullOrEmpty(invoice.NotificationURL))
|
||||
_JobClient.Schedule(() => NotifyHttp(invoiceStr), TimeSpan.Zero);
|
||||
}
|
||||
@ -97,30 +142,23 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
var job = NBitcoin.JsonConverters.Serializer.ToObject<ScheduledJob>(invoiceData);
|
||||
bool reschedule = false;
|
||||
var aggregatorEvent = new InvoiceIPNEvent(job.Notification.Data.Id, job.Notification.Event.Code, job.Notification.Event.Name);
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await SendNotification(job.Invoice, job.EventCode, job.Message, cts.Token);
|
||||
HttpResponseMessage response = await SendNotification(job.Notification, cts.Token);
|
||||
reschedule = !response.IsSuccessStatusCode;
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
|
||||
{
|
||||
Error = reschedule ? $"Unexpected return code: {(int)response.StatusCode}" : null
|
||||
});
|
||||
aggregatorEvent.Error = reschedule ? $"Unexpected return code: {(int)response.StatusCode}" : null;
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(aggregatorEvent);
|
||||
}
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
|
||||
{
|
||||
Error = "Timeout"
|
||||
});
|
||||
aggregatorEvent.Error = "Timeout";
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(aggregatorEvent);
|
||||
reschedule = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
|
||||
{
|
||||
Error = ex.Message
|
||||
});
|
||||
reschedule = true;
|
||||
|
||||
List<string> messages = new List<string>();
|
||||
@ -131,10 +169,8 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
string message = String.Join(',', messages.ToArray());
|
||||
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
|
||||
{
|
||||
Error = $"Unexpected error: {message}"
|
||||
});
|
||||
aggregatorEvent.Error = $"Unexpected error: {message}";
|
||||
_EventAggregator.Publish<InvoiceIPNEvent>(aggregatorEvent);
|
||||
}
|
||||
finally { cts?.Dispose(); }
|
||||
|
||||
@ -160,64 +196,35 @@ namespace BTCPayServer.HostedServices
|
||||
public InvoicePaymentNotificationEvent Event { get; set; }
|
||||
[JsonProperty("data")]
|
||||
public InvoicePaymentNotification Data { get; set; }
|
||||
[JsonProperty("extendedNotification")]
|
||||
public bool ExtendedNotification { get; set; }
|
||||
[JsonProperty(PropertyName = "notificationURL")]
|
||||
public string NotificationURL { get; set; }
|
||||
}
|
||||
|
||||
Encoding UTF8 = new UTF8Encoding(false);
|
||||
private async Task<HttpResponseMessage> SendNotification(InvoiceEntity invoice, int? eventCode, string name, CancellationToken cancellation)
|
||||
private async Task<HttpResponseMessage> SendNotification(InvoicePaymentNotificationEventWrapper notification, CancellationToken cancellation)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Method = HttpMethod.Post;
|
||||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
InvoicePaymentNotification notification = new InvoicePaymentNotification()
|
||||
{
|
||||
Id = dto.Id,
|
||||
Currency = dto.Currency,
|
||||
CurrentTime = dto.CurrentTime,
|
||||
ExceptionStatus = dto.ExceptionStatus,
|
||||
ExpirationTime = dto.ExpirationTime,
|
||||
InvoiceTime = dto.InvoiceTime,
|
||||
PosData = dto.PosData,
|
||||
Price = dto.Price,
|
||||
Status = dto.Status,
|
||||
BuyerFields = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", invoice.RefundMail) },
|
||||
PaymentSubtotals = dto.PaymentSubtotals,
|
||||
PaymentTotals = dto.PaymentTotals,
|
||||
AmountPaid = dto.AmountPaid,
|
||||
ExchangeRates = dto.ExchangeRates,
|
||||
var notificationString = NBitcoin.JsonConverters.Serializer.ToString(notification);
|
||||
var jobj = JObject.Parse(notificationString);
|
||||
|
||||
};
|
||||
|
||||
// We keep backward compatibility with bitpay by passing BTC info to the notification
|
||||
// we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
|
||||
var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetpaymentMethodId() == new PaymentMethodId("BTC", Payments.PaymentTypes.BTCLike));
|
||||
if (btcCryptoInfo != null)
|
||||
if (notification.ExtendedNotification)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
notification.Rate = dto.Rate;
|
||||
notification.Url = dto.Url;
|
||||
notification.BTCDue = dto.BTCDue;
|
||||
notification.BTCPaid = dto.BTCPaid;
|
||||
notification.BTCPrice = dto.BTCPrice;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
string notificationString = null;
|
||||
if (eventCode.HasValue)
|
||||
{
|
||||
var wrapper = new InvoicePaymentNotificationEventWrapper();
|
||||
wrapper.Data = notification;
|
||||
wrapper.Event = new InvoicePaymentNotificationEvent() { Code = eventCode.Value, Name = name };
|
||||
notificationString = JsonConvert.SerializeObject(wrapper);
|
||||
jobj.Remove("extendedNotification");
|
||||
jobj.Remove("notificationURL");
|
||||
notificationString = jobj.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationString = JsonConvert.SerializeObject(notification);
|
||||
notificationString = jobj["data"].ToString();
|
||||
}
|
||||
|
||||
request.RequestUri = new Uri(invoice.NotificationURL, UriKind.Absolute);
|
||||
request.RequestUri = new Uri(notification.NotificationURL, UriKind.Absolute);
|
||||
request.Content = new StringContent(notificationString, UTF8, "application/json");
|
||||
var response = await Enqueue(invoice.Id, async () => await _Client.SendAsync(request, cancellation));
|
||||
var response = await Enqueue(notification.Data.Id, async () => await _Client.SendAsync(request, cancellation));
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -306,17 +313,17 @@ namespace BTCPayServer.HostedServices
|
||||
e.Name == InvoiceEvent.Completed ||
|
||||
e.Name == InvoiceEvent.ExpiredPaidPartial
|
||||
)
|
||||
Notify(invoice);
|
||||
Notify(invoice, e, false);
|
||||
}
|
||||
|
||||
if (e.Name == "invoice_confirmed")
|
||||
if (e.Name == InvoiceEvent.Confirmed)
|
||||
{
|
||||
Notify(invoice);
|
||||
Notify(invoice, e, false);
|
||||
}
|
||||
|
||||
if (invoice.ExtendedNotifications)
|
||||
{
|
||||
Notify(invoice, e.EventCode, e.Name);
|
||||
Notify(invoice, e, true);
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -81,32 +81,35 @@ namespace BTCPayServer.Hosting
|
||||
|
||||
var isJson = (httpContext.Request.ContentType ?? string.Empty).StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
|
||||
var path = httpContext.Request.Path.Value;
|
||||
var method = httpContext.Request.Method;
|
||||
|
||||
isJson = method == "OPTIONS" ? true : isJson;
|
||||
if (
|
||||
bitpayAuth &&
|
||||
(path == "/invoices" || path == "/invoices/") &&
|
||||
httpContext.Request.Method == "POST" &&
|
||||
(method == "POST" || method == "OPTIONS") &&
|
||||
isJson)
|
||||
return true;
|
||||
|
||||
if (
|
||||
bitpayAuth &&
|
||||
(path == "/invoices" || path == "/invoices/") &&
|
||||
httpContext.Request.Method == "GET")
|
||||
(method == "GET" || method == "OPTIONS"))
|
||||
return true;
|
||||
|
||||
if (
|
||||
path.StartsWith("/invoices/", StringComparison.OrdinalIgnoreCase) &&
|
||||
httpContext.Request.Method == "GET" &&
|
||||
(method == "GET" || method == "OPTIONS") &&
|
||||
(isJson || httpContext.Request.Query.ContainsKey("token")))
|
||||
return true;
|
||||
|
||||
if (path.StartsWith("/rates", StringComparison.OrdinalIgnoreCase) &&
|
||||
httpContext.Request.Method == "GET")
|
||||
(method == "GET" || method == "OPTIONS"))
|
||||
return true;
|
||||
|
||||
if (
|
||||
path.Equals("/tokens", StringComparison.Ordinal) &&
|
||||
(httpContext.Request.Method == "GET" || httpContext.Request.Method == "POST"))
|
||||
(method == "GET" || method == "POST" || method == "OPTIONS"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
@ -92,14 +92,6 @@ namespace BTCPayServer.Hosting
|
||||
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||
options.Lockout.AllowedForNewUsers = true;
|
||||
});
|
||||
services.AddCors(o =>
|
||||
{
|
||||
o.AddPolicy("BitpayAPI", b =>
|
||||
{
|
||||
b.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
|
||||
});
|
||||
});
|
||||
|
||||
// If the HTTPS certificate path is not set this logic will NOT be used and the default Kestrel binding logic will be.
|
||||
string httpsCertificateFilePath = Configuration.GetOrDefault<string>("HttpsCertificateFilePath", null);
|
||||
bool useDefaultCertificate = Configuration.GetOrDefault<bool>("HttpsUseDefaultCertificate", false);
|
||||
|
@ -41,5 +41,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
[MaxLength(500)]
|
||||
[Display(Name = "Custom bootstrap CSS file")]
|
||||
public string CustomCSSLink { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,12 @@ namespace BTCPayServer.Models
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty("taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public decimal TaxIncluded
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
//"currency":"USD"
|
||||
[JsonProperty("currency")]
|
||||
public string Currency
|
||||
|
@ -105,6 +105,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string TaxIncluded { get; set; }
|
||||
public BuyerInformation BuyerInformation
|
||||
{
|
||||
get;
|
||||
@ -143,6 +144,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public DateTimeOffset MonitoringDate { get; internal set; }
|
||||
public List<Data.InvoiceEventData> Events { get; internal set; }
|
||||
public string NotificationEmail { get; internal set; }
|
||||
public Dictionary<string, string> PosData { get; set; }
|
||||
public Dictionary<string, object> PosData { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public bool IsModal { get; set; }
|
||||
public bool IsLightning { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string ServerUrl { get; set; }
|
||||
public string InvoiceId { get; set; }
|
||||
public string BtcAddress { get; set; }
|
||||
public string BtcDue { get; set; }
|
||||
|
80
BTCPayServer/Models/StatusMessageModel.cs
Normal file
80
BTCPayServer/Models/StatusMessageModel.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public class StatusMessageModel
|
||||
{
|
||||
public StatusMessageModel()
|
||||
{
|
||||
}
|
||||
|
||||
public StatusMessageModel(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (s.StartsWith("{", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
s.EndsWith("}", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var model = JObject.Parse(s).ToObject<StatusMessageModel>();
|
||||
Html = model.Html;
|
||||
Message = model.Message;
|
||||
Severity = model.Severity;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseNonJsonStatus(s);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ParseNonJsonStatus(s);
|
||||
}
|
||||
}
|
||||
|
||||
public string Message { get; set; }
|
||||
public string Html { get; set; }
|
||||
public StatusSeverity Severity { get; set; }
|
||||
|
||||
public string SeverityCSS
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Severity)
|
||||
{
|
||||
case StatusSeverity.Info:
|
||||
return "info";
|
||||
case StatusSeverity.Error:
|
||||
return "danger";
|
||||
case StatusSeverity.Success:
|
||||
return "success";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return JObject.FromObject(this).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
private void ParseNonJsonStatus(string s)
|
||||
{
|
||||
Message = s;
|
||||
Severity = s.StartsWith("Error", StringComparison.InvariantCultureIgnoreCase)
|
||||
? StatusSeverity.Error
|
||||
: StatusSeverity.Success;
|
||||
}
|
||||
|
||||
public enum StatusSeverity
|
||||
{
|
||||
Info,
|
||||
Error,
|
||||
Success
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
@ -11,16 +12,17 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class CheckoutExperienceViewModel
|
||||
{
|
||||
class Format
|
||||
public class Format
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public PaymentMethodId PaymentId { 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 the default payment method on checkout")]
|
||||
public string DefaultPaymentMethod { get; set; }
|
||||
[Display(Name = "Default language on checkout")]
|
||||
public string DefaultLang { get; set; }
|
||||
[Display(Name = "Do not propose lightning payment if value of the invoice is above...")]
|
||||
@ -47,15 +49,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
[Display(Name = "Custom HTML title to display on Checkout page")]
|
||||
public string HtmlTitle { 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 = langService.GetLanguages().Any(language => language.Code == defaultLang)? defaultLang : "en";
|
||||
|
@ -7,6 +7,7 @@ using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.JsonConverters;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
@ -16,17 +17,21 @@ namespace BTCPayServer.Payments.Lightning
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
public string BOLT11 { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
public uint256 PaymentHash { get; set; }
|
||||
|
||||
public string GetDestination(BTCPayNetwork network)
|
||||
{
|
||||
return GetPaymentId();
|
||||
return BOLT11;
|
||||
}
|
||||
|
||||
public decimal NetworkFee { get; set; }
|
||||
|
||||
|
||||
public string GetPaymentId()
|
||||
{
|
||||
return BOLT11;
|
||||
// Legacy, some old payments don't have the PaymentHash set
|
||||
return PaymentHash?.ToString() ?? BOLT11;
|
||||
}
|
||||
|
||||
public PaymentTypes GetPaymentType()
|
||||
|
@ -191,6 +191,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
var payment = await _InvoiceRepository.AddPayment(listenedInvoice.InvoiceId, notification.PaidAt.Value, new LightningLikePaymentData()
|
||||
{
|
||||
BOLT11 = notification.BOLT11,
|
||||
PaymentHash = BOLT11PaymentRequest.Parse(notification.BOLT11, network.NBitcoinNetwork).PaymentHash,
|
||||
Amount = notification.Amount
|
||||
}, network, accounted: true);
|
||||
if (payment != null)
|
||||
|
@ -91,6 +91,12 @@ namespace BTCPayServer.Services.Invoices
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public decimal TaxIncluded
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "currency")]
|
||||
public string Currency
|
||||
{
|
||||
|
@ -178,7 +178,6 @@ retry:
|
||||
textSearch.Add(invoice.StoreId);
|
||||
|
||||
AddToTextSearch(invoice.Id, textSearch.ToArray());
|
||||
|
||||
return invoice;
|
||||
}
|
||||
|
||||
|
@ -121,15 +121,18 @@ namespace BTCPayServer.Services.Rates
|
||||
var provider = GetNumberFormatInfo(currency, true);
|
||||
var currencyData = GetCurrencyData(currency, true);
|
||||
var divisibility = currencyData.Divisibility;
|
||||
while (true)
|
||||
if (value != 0m)
|
||||
{
|
||||
var rounded = decimal.Round(value, divisibility, MidpointRounding.AwayFromZero);
|
||||
if ((Math.Abs(rounded - value) / value) < 0.001m)
|
||||
while (true)
|
||||
{
|
||||
value = rounded;
|
||||
break;
|
||||
var rounded = decimal.Round(value, divisibility, MidpointRounding.AwayFromZero);
|
||||
if ((Math.Abs(rounded - value) / value) < 0.001m)
|
||||
{
|
||||
value = rounded;
|
||||
break;
|
||||
}
|
||||
divisibility++;
|
||||
}
|
||||
divisibility++;
|
||||
}
|
||||
if (divisibility != provider.CurrencyDecimalDigits)
|
||||
{
|
||||
|
@ -97,34 +97,83 @@
|
||||
<span asp-validation-for="Template" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<h5>Host button externally</h5>
|
||||
<p>You can host point of sale buttons in an external website with the following code.</p>
|
||||
@if (Model.Example1 != null)
|
||||
{
|
||||
<span>For anything with a custom amount</span>
|
||||
<pre><code class="html">@Model.Example1</code></pre>
|
||||
}
|
||||
@if (Model.Example2 != null)
|
||||
{
|
||||
<span>For a specific item of your template</span>
|
||||
<pre><code class="html">@Model.Example2</code></pre>
|
||||
}
|
||||
<p>A <code>POST</code> callback will be sent to notification with the following form will be sent to <code>notificationUrl</code> once the enough is paid and once again once there is enough confirmations to the payment:</p>
|
||||
<pre><code class="json">@Model.ExampleCallback</code></pre>
|
||||
<p><strong>Never</strong> trust anything but <code>id</code>, <strong>ignore</strong> the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:</p>
|
||||
<p>
|
||||
<ul>
|
||||
<li>Send a <code>GET</code> request to <code>https://btcpay.example.com/invoices/{invoiceId}</code> with <code>Content-Type: application/json</code></li>
|
||||
<li>Verify that the <code>orderId</code> is from your backend, that the <code>price</code> is correct and that <code>status</code> is either <code>confirmed</code> or <code>complete</code></li>
|
||||
<li>You can then ship your order</li>
|
||||
</ul>
|
||||
</p>
|
||||
<input type="submit" class="btn btn-primary" value="Save Settings" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-primary" value="Save Settings" />
|
||||
<div class="accordion" id="accordian-dev-info">
|
||||
<div class="card">
|
||||
<div class="card-header" id="accordian-dev-info-embed-payment-button-header">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#accordian-dev-info-embed-payment-button" aria-expanded="true" aria-controls="accordian-dev-info-embed-payment-button">
|
||||
Embed Payment Button linking to POS item
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div id="accordian-dev-info-embed-payment-button" class="collapse" aria-labelledby="accordian-dev-info-embed-payment-button-header" data-parent="#accordian-dev-info">
|
||||
<div class="card-body">
|
||||
<p>You can host point of sale buttons in an external website with the following code.</p>
|
||||
@if (Model.Example1 != null)
|
||||
{
|
||||
<span>For anything with a custom amount</span>
|
||||
<pre><code class="html">@Model.Example1</code></pre>
|
||||
}
|
||||
@if (Model.Example2 != null)
|
||||
{
|
||||
<span>For a specific item of your template</span>
|
||||
<pre><code class="html">@Model.Example2</code></pre>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header" id="accordian-dev-info-embed-pos-iframe-header">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link collapsed" type="button" data-toggle="collapse" data-target="#accordian-dev-info-embed-pos-iframe" aria-expanded="false" aria-controls="accordian-dev-info-embed-pos-iframe">
|
||||
Embed POS with Iframe
|
||||
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="accordian-dev-info-embed-pos-iframe" class="collapse" aria-labelledby="accordian-dev-info-embed-pos-iframe-header" data-parent="#accordian-dev-info">
|
||||
<div class="card-body">
|
||||
You can embed the POS using an iframe
|
||||
@{
|
||||
var iframe = $"<iframe src='{(Url.Action("ViewPointOfSale", "AppsPublic", new {appId = Model.Id}, Context.Request.Scheme))}' style='max-width: 100%; border: 0;'></iframe>";
|
||||
}
|
||||
<pre><code class="html">@iframe</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header" id="accordian-dev-info-notification-header">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link collapsed" type="button" data-toggle="collapse" data-target="#accordian-dev-info-notification" aria-expanded="false" aria-controls="accordian-dev-info-notification">
|
||||
Notification Url Callbacks
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="accordian-dev-info-notification" class="collapse" aria-labelledby="accordian-dev-info-notification-header" data-parent="#accordian-dev-info">
|
||||
<div class="card-body">
|
||||
<p>A <code>POST</code> callback will be sent to notification with the following form will be sent to <code>notificationUrl</code> once the enough is paid and once again once there is enough confirmations to the payment:</p>
|
||||
<pre><code class="json">@Model.ExampleCallback</code></pre>
|
||||
<p><strong>Never</strong> trust anything but <code>id</code>, <strong>ignore</strong> the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:</p>
|
||||
<p>
|
||||
<ul>
|
||||
<li>Send a <code>GET</code> request to <code>https://btcpay.example.com/invoices/{invoiceId}</code> with <code>Content-Type: application/json</code></li>
|
||||
<li>Verify that the <code>orderId</code> is from your backend, that the <code>price</code> is correct and that <code>status</code> is either <code>confirmed</code> or <code>complete</code></li>
|
||||
<li>You can then ship your order</li>
|
||||
</ul>
|
||||
</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
<a asp-action="ListApps">Back to the app list</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -194,6 +194,7 @@
|
||||
<div class="modal-footer bg-light">
|
||||
<form method="post" asp-antiforgery="false" data-buy>
|
||||
<input id="js-cart-amount" class="form-control" type="hidden" name="amount">
|
||||
<input id="js-cart-posdata" class="form-control" type="hidden" name="posdata">
|
||||
<button id="js-cart-pay" class="btn btn-primary btn-lg" type="submit"><b>@Model.CustomButtonText</b></button>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -390,7 +390,7 @@
|
||||
<span v-html="$t('Return to StoreName', srvModel)"></span>
|
||||
</a>
|
||||
<button class="action-button close-action" v-show="isModal">
|
||||
<span v-html="$t('home.header.title')">{{$t("Return to StoreName", srvModel)}}</span>
|
||||
<span v-html="$t('Close')">{{$t("Return to StoreName", srvModel)}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -155,7 +155,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Price</th>
|
||||
<td>@Model.ProductInformation.Price @Model.ProductInformation.Currency</td>
|
||||
<td>@Model.Fiat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tax included</th>
|
||||
<td>@Model.TaxIncluded</td>
|
||||
</tr>
|
||||
</table>
|
||||
}
|
||||
@ -178,30 +182,17 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Price</th>
|
||||
<td>@Model.ProductInformation.Price @Model.ProductInformation.Currency</td>
|
||||
<td>@Model.Fiat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tax included</th>
|
||||
<td>@Model.TaxIncluded</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h3>Point of Sale Data</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
@foreach (var posDataItem in Model.PosData)
|
||||
{
|
||||
<tr>
|
||||
@if (!string.IsNullOrEmpty(posDataItem.Key))
|
||||
{
|
||||
|
||||
<th>@posDataItem.Key</th>
|
||||
<td>@posDataItem.Value</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<td colspan="2">@posDataItem.Value</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
<partial name="PosData" model="@Model.PosData" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
37
BTCPayServer/Views/Invoice/PosData.cshtml
Normal file
37
BTCPayServer/Views/Invoice/PosData.cshtml
Normal file
@ -0,0 +1,37 @@
|
||||
@model Dictionary<string, object>
|
||||
|
||||
<table class="table table-sm table-responsive-md">
|
||||
@foreach (var posDataItem in Model)
|
||||
{
|
||||
<tr>
|
||||
@if (!string.IsNullOrEmpty(posDataItem.Key))
|
||||
{
|
||||
<th>@posDataItem.Key</th>
|
||||
<td>
|
||||
@if (posDataItem.Value is string)
|
||||
{
|
||||
@posDataItem.Value
|
||||
}
|
||||
else
|
||||
{
|
||||
<partial name="PosData" model="@posDataItem.Value"/>
|
||||
}
|
||||
|
||||
</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td colspan="2">
|
||||
@if (posDataItem.Value is string)
|
||||
{
|
||||
@posDataItem.Value
|
||||
}
|
||||
else
|
||||
{
|
||||
<partial name="PosData" model="@posDataItem.Value"/>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</table>
|
@ -1,10 +1,17 @@
|
||||
@model string
|
||||
|
||||
@if(!String.IsNullOrEmpty(Model))
|
||||
@if(!string.IsNullOrEmpty(Model))
|
||||
{
|
||||
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
|
||||
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
|
||||
var parsedModel = new StatusMessageModel(Model);
|
||||
<div class="alert alert-@parsedModel.SeverityCSS alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
@Model
|
||||
@if (!string.IsNullOrEmpty(parsedModel.Message))
|
||||
{
|
||||
@parsedModel.Message
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(parsedModel.Html))
|
||||
{
|
||||
@Html.Raw(parsedModel.Html)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -38,12 +38,17 @@
|
||||
</p>
|
||||
<div id="ledger-info" class="form-text text-muted" style="display: none;">
|
||||
<span>A ledger wallet is detected, which account do you want to use? No need to paste manually xpub if your ledger device was detected. Just select derivation scheme from the list bellow and xpub will automatically populate.</span>
|
||||
<ul>
|
||||
@for (int i = 0; i < 4; i++)
|
||||
{
|
||||
<li><a class="ledger-info-recommended" data-ledgerkeypath="@Model.RootKeyPath.Derive(i, true)" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a></li>
|
||||
}
|
||||
</ul>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" id="ledgerAccountsDropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Select ledger wallet account
|
||||
</button>
|
||||
<div class="dropdown-menu overflow-auto" style="max-height: 200px;" aria-labelledby="ledgerAccountsDropdownMenuButton">
|
||||
@for (var i = 0; i < 20; i++)
|
||||
{
|
||||
<a class="dropdown-item ledger-info-recommended" data-ledgerkeypath="@Model.RootKeyPath.Derive(i, true)" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -58,27 +63,27 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>P2WPKH</td>
|
||||
<td>xpub</td>
|
||||
<td>xpub...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>P2SH-P2WPKH</td>
|
||||
<td>xpub-[p2sh]</td>
|
||||
<td>xpub...-[p2sh]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>P2PKH</td>
|
||||
<td>xpub-[legacy]</td>
|
||||
<td>xpub...-[legacy]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Multi-sig P2WSH</td>
|
||||
<td>2-of-xpub1-xpub2</td>
|
||||
<td>2-of-xpub1...-xpub2...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Multi-sig P2SH-P2WSH</td>
|
||||
<td>2-of-xpub1-xpub2-[p2sh]</td>
|
||||
<td>2-of-xpub1...-xpub2...-[p2sh]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Multi-sig P2SH</td>
|
||||
<td>2-of-xpub1-xpub2-[legacy]</td>
|
||||
<td>2-of-xpub1...-xpub2...-[legacy]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -31,8 +31,8 @@
|
||||
<span asp-validation-for="HtmlTitle" 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>
|
||||
<label asp-for="DefaultPaymentMethod"></label>
|
||||
<select asp-for="DefaultPaymentMethod" asp-items="Model.CryptoCurrencies" class="form-control"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="DefaultLang"></label>
|
||||
|
@ -11,6 +11,9 @@
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form method="post">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
If you are enabling Changelly support, we advise that you configure the invoice expiration to a minimum of 30 minutes as it may take longer than the default 15 minutes to convert the funds.
|
||||
</div>
|
||||
<p>
|
||||
You can obtain API keys at
|
||||
<a href="https://changelly.com/?ref_id=804298eb5753" target="_blank">
|
||||
|
@ -11,8 +11,11 @@
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form method="post">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
If you are enabling CoinSwitch support, we advise that you configure the invoice expiration to a minimum of 30 minutes as it may take longer than the default 15 minutes to convert the funds.
|
||||
</div>
|
||||
<p>
|
||||
You can obtain a merchant id at
|
||||
You can obtain a merchant id at
|
||||
<a href="https://coinswitch.co/switch/setup/btcpay" target="_blank">
|
||||
https://coinswitch.co/switch/setup/btcpay
|
||||
</a>
|
||||
@ -24,10 +27,10 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Mode"></label>
|
||||
<select asp-for="Mode" asp-items="Model.Modes" class="form-control" >
|
||||
<select asp-for="Mode" asp-items="Model.Modes" class="form-control">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Enabled"></label>
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check"/>
|
||||
|
@ -29,7 +29,8 @@
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
If this wallet got restored, should have received money but nothing is showing up, please <a asp-action="WalletRescan">Rescan it</a>.
|
||||
If BTCPay Server shows you an invalid balance, <a asp-action="WalletRescan">rescan your wallet</a>. <br />
|
||||
If some transactions appear in BTCPay Server, but are missing on Electrum or another wallet, <a href="https://docs.btcpayserver.org/faq-and-common-issues/faq-wallet#missing-payments-in-my-software-or-hardware-wallet">follow those instructions</a>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@ -21,6 +21,7 @@ function Cart() {
|
||||
|
||||
this.updateItemsCount();
|
||||
this.updateAmount();
|
||||
this.updatePosData();
|
||||
}
|
||||
|
||||
Cart.prototype.setCustomAmount = function(amount) {
|
||||
@ -243,6 +244,7 @@ Cart.prototype.updateAll = function() {
|
||||
this.updateSummaryTotal();
|
||||
this.updateTotal();
|
||||
this.updateAmount();
|
||||
this.updatePosData();
|
||||
}
|
||||
|
||||
// Update number of cart items
|
||||
@ -290,6 +292,20 @@ Cart.prototype.updateTip = function(amount) {
|
||||
Cart.prototype.updateAmount = function() {
|
||||
$('#js-cart-amount').val(this.getTotal(true));
|
||||
}
|
||||
Cart.prototype.updatePosData = function() {
|
||||
|
||||
var result = {
|
||||
cart: this.content,
|
||||
customAmount: this.fromCents(this.getCustomAmount()),
|
||||
discountPercentage: this.discount? parseFloat(this.discount): 0,
|
||||
subTotal: this.fromCents(this.getTotalProducts()),
|
||||
discountAmount: this.fromCents(this.getDiscountAmount(this.totalAmount)),
|
||||
tip: this.tip? this.tip: 0,
|
||||
total: this.getTotal(true)
|
||||
};
|
||||
console.warn(result);
|
||||
$('#js-cart-posdata').val(JSON.stringify(result));
|
||||
}
|
||||
|
||||
Cart.prototype.resetDiscount = function() {
|
||||
this.setDiscount(0);
|
||||
@ -644,6 +660,7 @@ $.fn.inputAmount = function(obj, type) {
|
||||
|
||||
obj.updateSummaryTotal();
|
||||
obj.updateAmount();
|
||||
obj.updatePosData();
|
||||
obj.emptyCartToggle();
|
||||
});
|
||||
}
|
||||
@ -668,4 +685,4 @@ $.fn.removeAmount = function(obj, type) {
|
||||
obj.updateSummaryTotal();
|
||||
obj.emptyCartToggle();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -95,9 +95,8 @@ function onDataCallback(jsonData) {
|
||||
}
|
||||
|
||||
function fetchStatus() {
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/" + srvModel.paymentMethodId + "/status";
|
||||
$.ajax({
|
||||
url: path,
|
||||
url: window.location.pathname + "/status?invoiceId=" + srvModel.invoiceId + "&paymentMethodId=" + srvModel.paymentMethodId,
|
||||
type: "GET",
|
||||
cache: false
|
||||
}).done(function (data) {
|
||||
@ -164,11 +163,8 @@ $(document).ready(function () {
|
||||
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").addClass("loading");
|
||||
// Push the email to a server, once the reception is confirmed move on
|
||||
srvModel.customerEmail = emailAddress;
|
||||
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/UpdateCustomer";
|
||||
|
||||
$.ajax({
|
||||
url: path,
|
||||
url: window.location.pathname + "/UpdateCustomer?invoiceId=" + srvModel.invoiceId,
|
||||
type: "POST",
|
||||
data: JSON.stringify({ Email: srvModel.customerEmail }),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
@ -240,14 +236,22 @@ $(document).ready(function () {
|
||||
|
||||
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||
if (supportsWebSockets) {
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status/ws";
|
||||
path = path.replace("https://", "wss://");
|
||||
path = path.replace("http://", "ws://");
|
||||
var loc = window.location, ws_uri;
|
||||
if (loc.protocol === "https:") {
|
||||
ws_uri = "wss:";
|
||||
} else {
|
||||
ws_uri = "ws:";
|
||||
}
|
||||
ws_uri += "//" + loc.host;
|
||||
ws_uri += loc.pathname + "/status/ws?invoiceId=" + srvModel.invoiceId;
|
||||
try {
|
||||
var socket = new WebSocket(path);
|
||||
var socket = new WebSocket(ws_uri);
|
||||
socket.onmessage = function (e) {
|
||||
fetchStatus();
|
||||
};
|
||||
socket.onerror = function (e) {
|
||||
console.error("Error while connecting to websocket for invoice notifications (callback)");
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error while connecting to websocket for invoice notifications");
|
||||
|
BIN
BTCPayServer/wwwroot/imlegacy/bitcoinplus.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/bitcoinplus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.8 KiB |
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transakce",
|
||||
"txCount_plural": "{{count}} transakcí",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
"lang": "Sprache",
|
||||
"Awaiting Payment...": "Warten auf Zahlung...",
|
||||
"Pay with": "Bezahlen mit",
|
||||
"Contact and Refund Email": "Kontakt und Rückerstattungs Email",
|
||||
"Contact and Refund Email": "Kontakt und Rückerstattungs E-Mail",
|
||||
"Contact_Body": "Bitte geben Sie unten eine E-Mail-Adresse an. Wir werden Sie unter dieser Adresse kontaktieren, falls ein Problem mit Ihrer Zahlung vorliegt.",
|
||||
"Your email": "Ihre Email-Adresse",
|
||||
"Continue": "Fortsetzen",
|
||||
@ -24,16 +24,16 @@
|
||||
"Copied": "Kopiert",
|
||||
"ConversionTab_BodyTop": "Sie können {{btcDue}} {{cryptoCode}} mit Altcoins bezahlen, die nicht direkt vom Händler unterstützt werden.",
|
||||
"ConversionTab_BodyDesc": "Dieser Service wird von Drittanbietern bereitgestellt. Bitte beachten Sie, dass wir keine Kontrolle darüber haben, wie die Anbieter Ihre Gelder weiterleiten. Die Rechnung wird erst als bezahlt markiert, wenn das Geld in {{cryptoCode}} Blockchain eingegangen ist.",
|
||||
"ConversionTab_CalculateAmount_Error": "Retry",
|
||||
"ConversionTab_LoadCurrencies_Error": "Retry",
|
||||
"ConversionTab_CalculateAmount_Error": "Wiederholen",
|
||||
"ConversionTab_LoadCurrencies_Error": "Wiederholen",
|
||||
"ConversionTab_Lightning": "Für Lightning Network-Zahlungen sind keine Umrechnungsanbieter verfügbar.",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Please select a currency to convert from",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Bitte eine Ausgangswährung zum Geldwechsel auswählen",
|
||||
"Invoice expiring soon...": "Die Rechnung läuft bald ab...",
|
||||
"Invoice expired": "Die Rechnung ist abgelaufen",
|
||||
"What happened?": "Was ist passiert?",
|
||||
"InvoiceExpired_Body_1": "Diese Rechnung ist abgelaufen. Eine Rechnung ist nur für {{maxTimeMinutes}} Minuten gültig. \nSie können zu {{storeName}} zurückkehren, wenn Sie Ihre Zahlung erneut senden möchten.",
|
||||
"InvoiceExpired_Body_2": "Wenn Sie versucht haben, eine Zahlung zu senden, wurde sie vom Netzwerk noch nicht akzeptiert. Wir haben Ihre Gelder noch nicht erhalten.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"InvoiceExpired_Body_3": "Sollten Ihre Gelder zu einem späteren Zeitpunkt ankommen, werden wir entweder Ihren Auftrag bearbeiten oder Sie bezüglich der Rückerstattung kontaktieren...",
|
||||
"Invoice ID": "Rechnungs ID",
|
||||
"Order ID": "Auftrag ID",
|
||||
"Return to StoreName": "Zurück zu {{storeName}}",
|
||||
@ -42,8 +42,9 @@
|
||||
"Archived_Body": "Bitte kontaktieren Sie den Shop für Bestellinformationen oder Hilfe",
|
||||
"BOLT 11 Invoice": "BOLT 11 Rechnung",
|
||||
"Node Info": "Netzwerkknoten Info",
|
||||
"txCount": "{{count}} transaktion",
|
||||
"txCount_plural": "{{count}} transaktionen",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"txCount": "{{count}} Transaktion",
|
||||
"txCount_plural": "{{count}} Transaktionen",
|
||||
"Pay with CoinSwitch": "Zahlen mit CoinSwitch",
|
||||
"Pay with Changelly": "Zahlen mit Changelly",
|
||||
"Close": "Schließen"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transaction",
|
||||
"txCount_plural": "{{count}} transactions",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transacción",
|
||||
"txCount_plural": "{{count}} transacciones",
|
||||
"Pay with CoinSwitch": "Pagar con CoinSwitch",
|
||||
"Pay with Changelly": "Pagar con Changelly"
|
||||
"Pay with Changelly": "Pagar con Changelly",
|
||||
"Close": "Cerrar"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transaction",
|
||||
"txCount_plural": "{{count}} transactions",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "लेनदेन",
|
||||
"txCount_plural": "लेनदेनों",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transaction",
|
||||
"txCount_plural": "{{count}} transactions",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
50
BTCPayServer/wwwroot/locales/hu-HU.json
Normal file
50
BTCPayServer/wwwroot/locales/hu-HU.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"NOTICE_WARN": "THIS CODE HAS BEEN AUTOMATICALLY GENERATED FROM TRANSIFEX, IF YOU WISH TO HELP TRANSLATION COME ON THE SLACK http://slack.btcpayserver.org TO REQUEST PERMISSION TO https://www.transifex.com/btcpayserver/btcpayserver/",
|
||||
"code": "hu-HU",
|
||||
"currentLanguage": "Magyar",
|
||||
"lang": "Nyelv",
|
||||
"Awaiting Payment...": "A fizetés még folyamatban...",
|
||||
"Pay with": "Fizetési mód kiválasztása",
|
||||
"Contact and Refund Email": "e-mail cím kapcsolattartáshoz és visszautaláshoz",
|
||||
"Contact_Body": "Kérjük adja meg az e-mail címét. Abban az esetben ha felmerülne valami a fizetése kapcsán, ezen az e-mail címen fogjuk értesíteni.",
|
||||
"Your email": "e-mail címe",
|
||||
"Continue": "Tovább",
|
||||
"Please enter a valid email address": "Kérjük adjon meg egy érvényes e-mail címet",
|
||||
"Order Amount": "Rendelés összege",
|
||||
"Network Cost": "Hálozat költsége",
|
||||
"Already Paid": "Már befizetve",
|
||||
"Due": "Fennálló összeg",
|
||||
"Scan": "Szkennelés",
|
||||
"Copy": "Másolás",
|
||||
"Conversion": "Átváltás",
|
||||
"Open in wallet": "Nyisd ki a pénztárcában",
|
||||
"CompletePay_Body": "A fizetés végrehajtásához, kérjük küldjön {{btcDue}} {{cryptoCode}} az alábbi címre.",
|
||||
"Amount": "Összeg",
|
||||
"Address": "Cím",
|
||||
"Copied": "Másolva",
|
||||
"ConversionTab_BodyTop": "Fizethet {{btcDue}} {{cryptoCode}} olyan altcoin-okkal melyeket a kereskedő nem támogat közvetlenlül.",
|
||||
"ConversionTab_BodyDesc": "Ezt a szolgáltatást harmadik fél nyújtja. Kérjük figyelembe venni, hogy nem áll módunkban befolyásolni a szolgáltatók hogyan fogják továbbítani a pénzét. A számla csak akkor lesz fizetettként jelölve, miután a tranzakciója megérkezett a {{cryptoCode}} Blockchain-re. ",
|
||||
"ConversionTab_CalculateAmount_Error": "Próbáld újra",
|
||||
"ConversionTab_LoadCurrencies_Error": "Próbáld újra",
|
||||
"ConversionTab_Lightning": "A Lightning Network tranzakciók támogatására átváltási szolgáltató nem található",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Valuta kiválasztása a konvertáláshoz",
|
||||
"Invoice expiring soon...": "A számla rövidesen le fog járni...",
|
||||
"Invoice expired": "A számla lejárt",
|
||||
"What happened?": "Mi történt?",
|
||||
"InvoiceExpired_Body_1": "Ez a számla lejárt. A számla csak {{maxTimeMinutes}} percig érvényes. \nHa a fizetést újra szeretné indítani, visszaléphet a(z) {{storeName}} boltba ",
|
||||
"InvoiceExpired_Body_2": "Abban az esetben ha fizetni próbált, a hálozát nem fogadta el a tanzakciót. Befizetése még nem érkezett meg.",
|
||||
"InvoiceExpired_Body_3": "Ha a tranzakciója később érkezne meg hozzánk, megbízását feldolgozzuk, vagy a visszautalás megbeszélése érdekében, kapcsolatba lépünk Önnel... ",
|
||||
"Invoice ID": "Számla azonosító",
|
||||
"Order ID": "Megbízás azonosítója",
|
||||
"Return to StoreName": "Vissza a(z) {{storeName}} boltba",
|
||||
"This invoice has been paid": "A számla kifizetve",
|
||||
"This invoice has been archived": "A számla archiválva",
|
||||
"Archived_Body": "Rendelési információkhoz vagy ha támogatásra szorul, kérjük lépjen kapcsolatba az eladóval",
|
||||
"BOLT 11 Invoice": "BOLT 11 számla",
|
||||
"Node Info": "Node információ",
|
||||
"txCount": "{{count}} tranzakció",
|
||||
"txCount_plural": "{{count}} tranzakció",
|
||||
"Pay with CoinSwitch": "Fizess CoinSwitch-csel",
|
||||
"Pay with Changelly": "Fizess Changelly-vel",
|
||||
"Close": "Bezár"
|
||||
}
|
@ -24,10 +24,10 @@
|
||||
"Copied": "Afritað",
|
||||
"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í hvað þeir gera við peningana. Reikningurinn verður aðeins móttekinn þegar {{cryptoCode}} greiðslan hefur verið staðfest á netinu.",
|
||||
"ConversionTab_CalculateAmount_Error": "Retry",
|
||||
"ConversionTab_LoadCurrencies_Error": "Retry",
|
||||
"ConversionTab_CalculateAmount_Error": "reyndu aftur",
|
||||
"ConversionTab_LoadCurrencies_Error": "reyndu aftur",
|
||||
"ConversionTab_Lightning": "Engir viðskiptaveitendur eru í boði fyrir Lightning Network greiðslur.",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Please select a currency to convert from",
|
||||
"ConversionTab_CurrencyList_Select_Option": "veldu gjaldmiðil til að breyta frá",
|
||||
"Invoice expiring soon...": "Reikningurinn rennur út fljótlega...",
|
||||
"Invoice expired": "Reikningurinn er útrunnin",
|
||||
"What happened?": "Hvað gerðist?",
|
||||
@ -44,6 +44,7 @@
|
||||
"Node Info": "Nótu upplýsingar",
|
||||
"txCount": "{{count}} reikningur",
|
||||
"txCount_plural": "{{count}} reikningar",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with CoinSwitch": "Borga með Coinswitch",
|
||||
"Pay with Changelly": "Borgar með Changelly",
|
||||
"Close": "Loka"
|
||||
}
|
@ -23,27 +23,28 @@
|
||||
"Address": "Indirizzo",
|
||||
"Copied": "Copiato",
|
||||
"ConversionTab_BodyTop": "Puoi pagare {{btcDue}} {{cryptoCode}} usando altcoin diverse da quelle che il commerciante supporta direttamente.",
|
||||
"ConversionTab_BodyDesc": "Questo servizio è fornito da terze parti. Si prega di tenere presente che non abbiamo alcun controllo su come i fornitori inoltreranno i fondi. La fattura verrà contrassegnata solo dopo aver ricevuto i fondi su {{cryptoCode}} Blockchain.",
|
||||
"ConversionTab_CalculateAmount_Error": "Retry",
|
||||
"ConversionTab_LoadCurrencies_Error": "Retry",
|
||||
"ConversionTab_BodyDesc": "Questo servizio è fornito da terze parti. Ricorda che non abbiamo alcun controllo su come tali parti inoltreranno i fondi. La fattura verrà contrassegnata come pagata solo dopo aver ricevuto i fondi su {{cryptoCode}} Blockchain.",
|
||||
"ConversionTab_CalculateAmount_Error": "Riprova",
|
||||
"ConversionTab_LoadCurrencies_Error": "Riprova",
|
||||
"ConversionTab_Lightning": "Nessun fornitore di conversione disponibile per i pagamenti Lightning Network.",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Please select a currency to convert from",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Seleziona una valuta da convertire",
|
||||
"Invoice expiring soon...": "Fattura in scadenza a breve...",
|
||||
"Invoice expired": "Fattura scaduta",
|
||||
"What happened?": "Cosa è successo?",
|
||||
"InvoiceExpired_Body_1": "Questa fattura è scaduta. Una fattura è valida solo per {{maxTime minuti}} minuti. \nPuoi tornare a {{store name}} se desideri inviare nuovamente il pagamento.",
|
||||
"InvoiceExpired_Body_2": "Se hai provato a inviare un pagamento, non è ancora stato accettato dalla rete. Non abbiamo ancora ricevuto i tuoi fondi.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"InvoiceExpired_Body_1": "Questa fattura è scaduta. Una fattura è valida solo per {{maxTimeMinutes}} minuti. \nPuoi tornare a {{storeName}} se desideri inviare nuovamente il pagamento.",
|
||||
"InvoiceExpired_Body_2": "Se hai provato ad inviare un pagamento, non è ancora stato accettato dalla rete. Non abbiamo ancora ricevuto i tuoi fondi.",
|
||||
"InvoiceExpired_Body_3": "Se lo riceviamo in un secondo momento, processeremo comunque il tuo ordine o ti contatteremo per rimborsarti...",
|
||||
"Invoice ID": "Numero della Fattura",
|
||||
"Order ID": "Numero dell'Ordine",
|
||||
"Return to StoreName": "Ritorna a {{storeName}}",
|
||||
"This invoice has been paid": "La fattura è stata pagata",
|
||||
"This invoice has been archived": "TQuesta fattura è stata pagata",
|
||||
"This invoice has been archived": "Questa fattura è stata archiviata",
|
||||
"Archived_Body": "Contatta il negozio per informazioni sull'ordine o per assistenza",
|
||||
"BOLT 11 Invoice": "Fattura BOLT 11",
|
||||
"Node Info": "Informazioni sul Nodo",
|
||||
"txCount": "{{count}} transazione",
|
||||
"txCount_plural": "{{count}} transazioni",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with CoinSwitch": "Paga con CoinSwitch",
|
||||
"Pay with Changelly": "Paga con Changelly",
|
||||
"Close": "Chiudi"
|
||||
}
|
@ -44,6 +44,7 @@
|
||||
"Node Info": "接続情報",
|
||||
"txCount": "取引 {{count}} 個",
|
||||
"txCount_plural": "取引 {{count}} 個",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with CoinSwitch": "CoinSwitchでのお支払い",
|
||||
"Pay with Changelly": "Changellyでのお支払い",
|
||||
"Close": "閉じる"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} Транзакция",
|
||||
"txCount_plural": "{{count}} Транзакциялар",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -18,22 +18,22 @@
|
||||
"Copy": "Kopiëren",
|
||||
"Conversion": "Omzetting",
|
||||
"Open in wallet": "Wallet openen",
|
||||
"CompletePay_Body": "Om de betaling te vervoledigen, bedankt om {{btcDue}} {{cryptoCode}} naar het hieronder vemelde adres op te sturen.",
|
||||
"CompletePay_Body": "Om de betaling af te ronden, stuur alstublieft {{btcDue}} {{cryptoCode}} naar het hieronder vemelde adres.",
|
||||
"Amount": "Bedrag",
|
||||
"Address": "Adres",
|
||||
"Copied": "Gekopieerd",
|
||||
"ConversionTab_BodyTop": "Je kan altcoins gebruiken die niet ondersteund zijn door de verkoper, om {{btcDue}} {{cryptoCode}} te betalen.",
|
||||
"ConversionTab_BodyDesc": "Deze dienst wordt door een externe partij geleverd. Bijgevolg, hebben we geen zicht over jouw fondsen. De factuur wordt pas als betaald beschouwd, wanneer de fondsen door de blockchain aanvaard zijn {{ cryptoCode }}.",
|
||||
"ConversionTab_CalculateAmount_Error": "Retry",
|
||||
"ConversionTab_LoadCurrencies_Error": "Retry",
|
||||
"ConversionTab_CalculateAmount_Error": "Opnieuw proberen",
|
||||
"ConversionTab_LoadCurrencies_Error": "Opnieuw proberen",
|
||||
"ConversionTab_Lightning": "Geen leverancier beschikbaar voor de betalingen op het Lightning Network",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Please select a currency to convert from",
|
||||
"ConversionTab_CurrencyList_Select_Option": "Selecteer een valuta om te converteren",
|
||||
"Invoice expiring soon...": "De factuur verloopt binnenkort...",
|
||||
"Invoice expired": "Vervallen factuur",
|
||||
"What happened?": "Wat gebeurde er?",
|
||||
"InvoiceExpired_Body_1": "De factuur is vervallen. Een factuur is alleen geldig voor {{maxTimeMinutes}} minuten. \nJe kan terug komen naar {{storeName}} als je de betaling opnieuw wilt proberen",
|
||||
"InvoiceExpired_Body_2": "Als je een betaling uitvoerde, dan werd dit nog niet bevestigd door het netwerk. We hebben je fondsen nog niet ontvangen.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"InvoiceExpired_Body_3": "Indien we het later ontvangen, zullen we uw order verwerken of nemen we contact op om een terugbetaling te regelen...",
|
||||
"Invoice ID": "Factuurnummer",
|
||||
"Order ID": "Bestllingsnummer",
|
||||
"Return to StoreName": "Terug naar {{storeName}}",
|
||||
@ -44,6 +44,7 @@
|
||||
"Node Info": "Node Info",
|
||||
"txCount": "{{count}} transactie",
|
||||
"txCount_plural": "{{count}} transacties",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with CoinSwitch": "Betalen met CoinSwitch",
|
||||
"Pay with Changelly": "Betalen met Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} लेनदेन",
|
||||
"txCount_plural": "{{count}} लेनदेन",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
"What happened?": "Co się stało?",
|
||||
"InvoiceExpired_Body_1": "Płatność wygasła. Płatność jest prawidłowa przez {{maxTimeMinutes}} minut. Jeśli chcesz wysłaść płatność ponownie wróć do {{storeName}}.",
|
||||
"InvoiceExpired_Body_2": "Jeżeli próbowałeś wysłać płatność to nie została ona zaakcepowana przez sieć. Nie otrzymaliśmy jeszcze twoich funduszy.",
|
||||
"InvoiceExpired_Body_3": "",
|
||||
"InvoiceExpired_Body_3": "Jeśli otrzymamy go w późniejszym terminie, wykonamy transakcję lub skontaktujemy się z tobą aby uzgodnić zwrot",
|
||||
"Invoice ID": "Płatność ID",
|
||||
"Order ID": "Zamówienie ID",
|
||||
"Return to StoreName": "Wróć do {{storeName}}",
|
||||
@ -44,6 +44,7 @@
|
||||
"Node Info": "Informacja o węźle",
|
||||
"txCount": "{{count}} transakcja",
|
||||
"txCount_plural": "{{count}} transakcji",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with CoinSwitch": "Zapłać z CoinSwitch",
|
||||
"Pay with Changelly": "Zapłać z Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transação",
|
||||
"txCount_plural": "{{count}} transações",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transação",
|
||||
"txCount_plural": "{{count}} transações",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} Сделка",
|
||||
"txCount_plural": "{{count}} операции",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transakcia",
|
||||
"txCount_plural": "{{count}} transakcií",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} transakcija",
|
||||
"txCount_plural": "{{count}} transakcij/e",
|
||||
"Pay with CoinSwitch": "Plačilo z CoinSwitch",
|
||||
"Pay with Changelly": "Plačilo z Changelly"
|
||||
"Pay with Changelly": "Plačilo z Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
"Address": "Adresa",
|
||||
"Copied": "Kopirano",
|
||||
"ConversionTab_BodyTop": "Možete platiti {{btcDue}} {{cryptoCode}} pomoću altcoina koje prodavac ne podržava direktno.",
|
||||
"ConversionTab_BodyDesc": "Ovu usluga pruža treća strana. Vodite računa da nemamo kontroliu nad načinom kako će Vam davatelji usluge proslijediti sredstva. Račun je plaćen tek kada su sredstva primljena na {{cryptoCode}} blokčejnu.",
|
||||
"ConversionTab_BodyDesc": "Ovu uslugu pruža treća strana. Vodite računa da nemamo kontroliu nad načinom kako će Vam davatelji usluge proslijediti sredstva. Račun je plaćen tek kada su sredstva primljena na {{cryptoCode}} blokčejnu.",
|
||||
"ConversionTab_CalculateAmount_Error": "Pokušaj ponovo",
|
||||
"ConversionTab_LoadCurrencies_Error": "Pokušaj ponovo",
|
||||
"ConversionTab_Lightning": "Ne postoji provajder koji će konvertovati Lightning Network uplate.",
|
||||
@ -31,7 +31,7 @@
|
||||
"Invoice expiring soon...": "Račun uskoro ističe...",
|
||||
"Invoice expired": "Račun je istekao",
|
||||
"What happened?": "Šta se desilo?",
|
||||
"InvoiceExpired_Body_1": "Ovaj račun je istekao. Račun važi samo {{maxTimeMinutes}} minuta. \nMožete se vratiti na {{storeName}} ukoliko želite da ponovo inicirate plaćanje.",
|
||||
"InvoiceExpired_Body_1": "Ovaj račun je istekao. Račun važi samo {{maxTimeMinutes}} minuta. \nMožete se vratiti na {{storeName}} ukoliko želite da ponovo inicirate plaćanje.",
|
||||
"InvoiceExpired_Body_2": "Ako ste pokušali da pošaljete uplatu, još uvek nije prihvaćena na mreži. Nismo još zaprimili Vašu uplatu.",
|
||||
"InvoiceExpired_Body_3": "Ukoliko vašu uplatu primimo kasnije, procesuiraćemo vašu narudžbinu ili ćemo vas kontaktirati za povraćaj novca...",
|
||||
"Invoice ID": "Broj računa",
|
||||
@ -39,11 +39,12 @@
|
||||
"Return to StoreName": "Vrati se na {{storeName}}",
|
||||
"This invoice has been paid": "Račun je plaćen.",
|
||||
"This invoice has been archived": "Račun je arhiviran.",
|
||||
"Archived_Body": "Molimo kontaktirajte prodaca za bili kakvu informaciju ili pomoć u vezi narudžbine.",
|
||||
"Archived_Body": "Molimo kontaktirajte prodavca za bili kakvu informaciju ili pomoć u vezi narudžbine.",
|
||||
"BOLT 11 Invoice": "BOLT 11 Račun",
|
||||
"Node Info": "Informacije Node-a",
|
||||
"txCount": "{{count}} transakcija",
|
||||
"txCount_plural": "{{count}} transakcije",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with CoinSwitch": "Plati putem CoinSwitch-a",
|
||||
"Pay with Changelly": "Plati putem Changelly-ja",
|
||||
"Close": "Zatvori"
|
||||
}
|
@ -44,6 +44,7 @@
|
||||
"Node Info": "Düğüm Bilgisi",
|
||||
"txCount": "{{count}} işlem",
|
||||
"txCount_plural": "{{count}} işlem",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with CoinSwitch": "CoinSwitch ile Öde",
|
||||
"Pay with Changelly": "Changelly ile Öde",
|
||||
"Close": "Kapat"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "транзакція {{count}}",
|
||||
"txCount_plural": "транзакції {{count}}",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}} giao dịch",
|
||||
"txCount_plural": "{{count}} các giao dịch",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
@ -45,5 +45,6 @@
|
||||
"txCount": "{{count}}笔交易",
|
||||
"txCount_plural": "{{count}}笔交易",
|
||||
"Pay with CoinSwitch": "Pay with CoinSwitch",
|
||||
"Pay with Changelly": "Pay with Changelly"
|
||||
"Pay with Changelly": "Pay with Changelly",
|
||||
"Close": "Close"
|
||||
}
|
56
README.md
56
README.md
@ -19,46 +19,52 @@ You can run BTCPay as a self-hosted solution on your own server, or use a [third
|
||||
|
||||
The self-hosted solution allows you not only to attach an unlimited number of stores and use the Lightning Network but also become the payment processor for others.
|
||||
|
||||
Thanks to the apps built on top of it, you can use BTCPay to receive donations or have an in-store POS system.
|
||||
Thanks to the [apps](https://github.com/btcpayserver/btcpayserver-doc/blob/master/Apps.md) built on top of it, you can use BTCPay to receive donations, start a crowdfunding campaign or have an in-store Point of Sale.
|
||||
|
||||
## Features
|
||||
|
||||
* Direct, P2P Bitcoin payments
|
||||
* Lightning Network support (LND and c-lightning)
|
||||
* Altcoin support
|
||||
* Complete control over private keys
|
||||
* Full compatibility with BitPay API (easy migration)
|
||||
* Enhanced privacy
|
||||
* SegWit support
|
||||
* Process payments for others
|
||||
* Payment buttons
|
||||
* Point of sale
|
||||
* Direct, peer-to-peer Bitcoin and altcoin payments
|
||||
* No transaction fees (other than those for the crypto networks)
|
||||
* No processing fees
|
||||
* No middleman
|
||||
* No KYC
|
||||
* User has complete control over private keys
|
||||
* Enhanced privacy
|
||||
* Enhanced security
|
||||
* Self-hosted
|
||||
* SegWit support
|
||||
* Lightning Network support (LND and c-lightning)
|
||||
* Altcoin support
|
||||
* Full compatibility with BitPay API (easy migration)
|
||||
* Process payments for others
|
||||
* Easy-embeddable Payment buttons
|
||||
* Point of sale app
|
||||
* Crowdfunding app
|
||||
|
||||
## Supported Altcoins
|
||||
|
||||
In addition to Bitcoin, BTCPay supports the following cryptocurrencies:
|
||||
Bitcoin is the only focus of the project and its core developers. However, support is implemented for several altcoins:
|
||||
|
||||
* BGold
|
||||
* Bitcore
|
||||
* Dash
|
||||
* Dogecoin
|
||||
* Feathercoin
|
||||
* Groestlcoin
|
||||
* Litecoin
|
||||
* Monacoin
|
||||
* Polis
|
||||
* UFO
|
||||
* Viacoin
|
||||
* Bitcoin Gold (BTG)
|
||||
* Bitcoin Plus (XBC)
|
||||
* Bitcore (BTX)
|
||||
* Dash (DASH)
|
||||
* Dogecoin (DOGE)
|
||||
* Feathercoin (FTC)
|
||||
* Groestlcoin (GRS)
|
||||
* Litecoin (LTC)
|
||||
* Monacoin (MONA)
|
||||
* Polis (POLIS)
|
||||
* Viacoin (VIA)
|
||||
|
||||
Altcoins are maintained by their respective communities.
|
||||
|
||||
## Documentation
|
||||
|
||||
Please check out our [complete documentation](https://github.com/btcpayserver/btcpayserver-doc) for more details.
|
||||
Please check out our [complete documentation](https://github.com/btcpayserver/btcpayserver-doc) and [FAQ](https://github.com/btcpayserver/btcpayserver-doc/tree/master/FAQ#btcpay-frequently-asked-questions-and-common-issues) for more details.
|
||||
|
||||
You can also read the [BTCPay Merchants Guide](https://www.reddit.com/r/Bitcoin/comments/8f1eqf/the_ultimate_guide_to_btcpay_the_free_and/).
|
||||
If you have any troubles with BTCPay, please file a [Github issue](https://github.com/btcpayserver/btcpayserver/issues).
|
||||
For general questions, please join the community chat on [Mattermost](https://chat.btcpayserver.org/).
|
||||
|
||||
## How to build
|
||||
|
||||
|
Reference in New Issue
Block a user