Compare commits

...

43 Commits

Author SHA1 Message Date
9952cdca7f bump 2018-10-17 12:06:37 +09:00
6278145374 Removing old QR update code (#337) 2018-10-17 11:55:49 +09:00
84018a5caa Bugfixing race condition for QR code switch (#335)
Ref: #334
2018-10-17 11:49:30 +09:00
d7785fe2d2 Added Serilog file logger for debug file support. (#323) 2018-10-16 00:37:42 +09:00
e1751c4d91 [Fix] Querying rate with authenticated request should be successfull 2018-10-15 18:11:20 +09:00
913da79ff4 Remove dups lang 2018-10-14 21:35:21 +09:00
a4fbb2de7e creating italian localization file (#310) 2018-10-14 21:34:15 +09:00
b5601ed5e6 fix typo (#304) 2018-10-14 21:28:09 +09:00
42c4f15f22 fixed typos (#331) 2018-10-14 21:26:47 +09:00
6fbd9b2628 bump 2018-10-12 14:02:27 +09:00
d04bfb58a2 Resolving issue with long translations breaking layout (#330) 2018-10-12 14:01:44 +09:00
cded2548f5 bump 2018-10-12 13:32:04 +09:00
dcc859a86a Disable export to JSON 2018-10-12 13:17:38 +09:00
9bec38559f Nepali Translation (#311)
* Nepali Language

* Delete Checkout.cshtml

* Delete LanguageService.cs

* add np.js

* Match Language File Style
2018-10-12 10:11:03 +09:00
2856454d41 [langs-fr] Correction and some minor improvement (#316) 2018-10-12 10:10:42 +09:00
b3c4fc4003 Added Russian (#312) 2018-10-12 10:10:20 +09:00
c2bbc04c4c Various bugfixes (#308)
* NotifyEmail field on Invoice, sending email when triggered

* Styling invoices page

* Exporting Invoices in JSON

* Recoding based on feedback

* Fixing image breaking responsive layout on mobile

* Reducing amount of data sent in email notification

* Turning bundling on by default
2018-10-12 10:09:13 +09:00
db40c7bc32 Solving the new version of btcpayserver caused btcpay-python not to create an order problem (#327) 2018-10-11 23:50:28 +09:00
60707fdbd1 Add simplified chinese translation (#326)
* Add Chinese Simplified translation

* Add Chinese simplified translation
2018-10-11 14:25:16 +09:00
f05614f4da bump 2018-10-11 00:51:43 +09:00
a10c382bd4 [Tests] return WalletId when registering scheme 2018-10-10 00:13:37 +09:00
da2fb876cb Can pass pre filled amount and address to Send Wallet 2018-10-09 23:48:14 +09:00
3c58bff803 Comparable WalletId 2018-10-09 23:44:32 +09:00
a28814bc0e Fix RateRules crash if dups 2018-10-09 23:43:03 +09:00
3cff8261ae Set the id for apps only to 20 bytes, and output the Id in the controller so we can use it in tests 2018-10-09 23:38:56 +09:00
b16e8f7b76 Move GetAppDataIfOwner inside AppHelper 2018-10-09 23:34:51 +09:00
57ab001c0c [Tests] Pass Port to the fake http context 2018-10-09 23:32:47 +09:00
3d85dace38 Move currency display method into CurrencyNameTable 2018-10-09 23:30:26 +09:00
7a04c2974f Center QR Authenticator QR Code 2018-10-09 23:30:26 +09:00
d459839bf7 Displaying Node Info as QR code for Lightning payments (#318)
* Styling elements required for Node info

* Allowing switching QR between bolt11 and node info for lightning

* Equal width for Bolt11 and Node info buttons

* Certain languages were too verbose for display of "Pay with"

Fixes: #317
2018-10-08 07:19:55 +09:00
657cfe1b23 bump 2018-10-06 23:21:33 +09:00
f4eaa0f01f Make sure X-Forwarded-Port does not override ExternalUrl 2018-10-06 23:20:32 +09:00
1e2ffcadf0 Add GRS lightning image (#309) 2018-10-03 11:25:52 +09:00
dcc05af02e fix typo 2018-10-02 22:11:01 +09:00
4b7f78f38b document .net version update 2018-10-02 22:06:06 +09:00
f94ff4cc74 Update dotnet version + nbxplorer 2018-10-02 19:33:25 +09:00
b750663a1f update lnd/clightning 2018-09-28 17:08:51 +09:00
4c4b76e995 Remove Bitpay direct provider 2018-09-28 17:08:51 +09:00
da19d2c1a7 Bugfixing display of custom amount entry in POS (#302)
Ref: #293
2018-09-28 13:31:59 +09:00
fb15c5b354 Increase timeout for wallet, update references 2018-09-28 10:15:35 +09:00
6ffe1cfcab bump LedgerWallet lib 2018-09-27 16:31:33 +09:00
87678c58ac Fix error message for ledger signing 2018-09-27 15:43:34 +09:00
feab4cc48a update ledgerwebsocket 2018-09-27 15:21:26 +09:00
53 changed files with 758 additions and 226 deletions

View File

@ -201,7 +201,7 @@ namespace BTCPayServer.Tests
public T GetController<T>(string userId = null, string storeId = null) where T : Controller
{
var context = new DefaultHttpContext();
context.Request.Host = new HostString("127.0.0.1");
context.Request.Host = new HostString("127.0.0.1", Port);
context.Request.Scheme = "http";
context.Request.Protocol = "http";
if (userId != null)

View File

@ -10,6 +10,19 @@ namespace BTCPayServer.Tests
{
public class RateRulesTest
{
[Fact]
public void SecondDuplicatedRuleIsIgnored()
{
StringBuilder builder = new StringBuilder();
builder.AppendLine("DOGE_X = 1.1");
builder.AppendLine("DOGE_X = 1.2");
Assert.True(RateRules.TryParse(builder.ToString(), out var rules));
var rule = rules.GetRuleFor(new CurrencyPair("DOGE", "BTC"));
rule.Reevaluate();
Assert.True(!rule.HasError);
Assert.Equal(1.1m, rule.BidAsk.Ask);
}
[Fact]
public void CanParseRateRules()
{

View File

@ -73,11 +73,11 @@ namespace BTCPayServer.Tests
public BTCPayNetwork SupportedNetwork { get; set; }
public void RegisterDerivationScheme(string crytoCode)
public WalletId RegisterDerivationScheme(string crytoCode)
{
RegisterDerivationSchemeAsync(crytoCode).GetAwaiter().GetResult();
return RegisterDerivationSchemeAsync(crytoCode).GetAwaiter().GetResult();
}
public async Task RegisterDerivationSchemeAsync(string cryptoCode)
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode)
{
SupportedNetwork = parent.NetworkProvider.GetNetwork(cryptoCode);
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId);
@ -92,6 +92,8 @@ namespace BTCPayServer.Tests
DerivationScheme = DerivationScheme.ToString(),
Confirmation = true
}, cryptoCode);
return new WalletId(StoreId, cryptoCode);
}
public DerivationStrategyBase DerivationScheme { get; set; }

View File

@ -332,7 +332,7 @@ namespace BTCPayServer.Tests
(0.1m, "$0.10 (USD)"),
})
{
var actual = InvoiceController.FormatCurrency(test.Item1, "USD", new CurrencyNameTable());
var actual = new CurrencyNameTable().DisplayFormatCurrency(test.Item1, "USD");
Assert.Equal(test.Item2, actual);
}
}
@ -641,6 +641,13 @@ namespace BTCPayServer.Tests
Assert.NotNull(GetCurrencyPairRateResult);
Assert.NotNull(GetCurrencyPairRateResult.Data);
Assert.Equal("LTC", GetCurrencyPairRateResult.Data.Code);
// Should be OK because the request is signed, so we can know the store
var rates = acc.BitPay.GetRates();
HttpClient client = new HttpClient();
// Unauthentified requests should also be ok
var response = client.GetAsync($"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}").GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
}
}
@ -1359,6 +1366,7 @@ namespace BTCPayServer.Tests
Assert.Single(appList.Apps);
Assert.Empty(appList2.Apps);
Assert.Equal("test", appList.Apps[0].AppName);
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
Assert.True(appList.Apps[0].IsOwner);
Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id).Result);

View File

@ -63,7 +63,7 @@ services:
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.2.31
image: nicolasdorier/nbxplorer:1.0.3.3
ports:
- "32838:32838"
expose:
@ -97,21 +97,21 @@ services:
rpcport=43782
port=39388
whitelist=0.0.0.0/0
zmqpubrawtx=tcp://0.0.0.0:28332
zmqpubrawblock=tcp://0.0.0.0:28332
zmqpubrawtxlock=tcp://0.0.0.0:28332
zmqpubhashblock=tcp://0.0.0.0:28332
zmqpubrawtx=tcp://0.0.0.0:28333
ports:
- "43782:43782"
- "28332:28332"
expose:
- "43782" # RPC
- "39388" # P2P
- "28332" # ZMQ
- "28333" # ZMQ
volumes:
- "bitcoin_datadir:/data"
customer_lightningd:
image: nicolasdorier/clightning:634f19a7b230edc686be56ab950b80784e56252c-dev
image: nicolasdorier/clightning:v0.6.1-dev
environment:
EXPOSE_TCP: "true"
LIGHTNINGD_OPT: |
@ -135,7 +135,7 @@ services:
- bitcoind
lightning-charged:
image: shesek/lightning-charge:0.3.15
image: shesek/lightning-charge:0.4.3
environment:
NETWORK: regtest
API_TOKEN: foiewnccewuify
@ -154,7 +154,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: nicolasdorier/clightning:634f19a7b230edc686be56ab950b80784e56252c-dev
image: nicolasdorier/clightning:v0.6.1-dev
environment:
EXPOSE_TCP: "true"
LIGHTNINGD_OPT: |
@ -201,7 +201,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:0.4.2.0
image: btcpayserver/lnd:0.5-beta
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
@ -209,11 +209,12 @@ services:
restlisten=0.0.0.0:8080
bitcoin.node=bitcoind
bitcoind.rpchost=bitcoind:43782
bitcoind.zmqpath=tcp://bitcoind:28332
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
externalip=merchant_lnd:9735
no-macaroons=1
debuglevel=debug
noencryptwallet=1
noseedbackup=1
trickledelay=1000
ports:
- "53280:8080"
@ -226,7 +227,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:0.4.2.0
image: btcpayserver/lnd:0.5-beta
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
@ -234,11 +235,12 @@ services:
restlisten=0.0.0.0:8080
bitcoin.node=bitcoind
bitcoind.rpchost=bitcoind:43782
bitcoind.zmqpath=tcp://bitcoind:28332
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
externalip=customer_lnd:10009
no-macaroons=1
debuglevel=debug
noencryptwallet=1
noseedbackup=1
trickledelay=1000
ports:
- "53281:8080"

View File

@ -26,6 +26,7 @@ namespace BTCPayServer
"GRS_BTC = bittrex(GRS_BTC)"
},
CryptoImagePath = "imlegacy/groestlcoin.png",
LightningImagePath = "imlegacy/groestlcoin-lightning.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'")
});

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<Version>1.0.2.105</Version>
<Version>1.0.2.113</Version>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
</PropertyGroup>
<PropertyGroup>
@ -33,34 +33,36 @@
<EmbeddedResource Include="Currencies.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.0" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.0.1" />
<PackageReference Include="BuildBundlerMinifier" Version="2.7.385" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.5.3" />
<PackageReference Include="Hangfire" Version="1.6.19" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.2" />
<PackageReference Include="LedgerWallet" Version="2.0.0.1" />
<PackageReference Include="LedgerWallet" Version="2.0.0.2" />
<PackageReference Include="Meziantou.AspNetCore.BundleTagHelpers" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.0" />
<PackageReference Include="NBitcoin" Version="4.1.1.46" />
<PackageReference Include="NBitcoin" Version="4.1.1.48" />
<PackageReference Include="NBitpayClient" Version="1.0.0.30" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.2.18" />
<PackageReference Include="NBXplorer.Client" Version="1.0.3" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
<PackageReference Include="NicolasDorier.RateLimits" Version="1.0.0.3" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.17" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1.0" />
<PackageReference Include="Serilog" Version="2.7.1" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="SSH.NET" Version="2016.1.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
<PackageReference Include="Text.Analyzers" Version="2.6.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version=" 2.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.4" />
<PackageReference Include="YamlDotNet" Version="4.3.1" />
</ItemGroup>

View File

@ -39,6 +39,7 @@ namespace BTCPayServer.Configuration
app.Option("--sshkeyfile", "SSH private key file to manage BTCPay (default: empty)", CommandOptionType.SingleValue);
app.Option("--sshkeyfilepassword", "Password of the SSH keyfile (default: empty)", CommandOptionType.SingleValue);
app.Option("--sshtrustedfingerprints", "SSH Host public key fingerprint or sha256 (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue);
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
foreach (var network in provider.GetAll())
{
var crypto = network.CryptoCode.ToLowerInvariant();

View File

@ -40,6 +40,7 @@ namespace BTCPayServer.Controllers
[TempData]
public string StatusMessage { get; set; }
public string CreatedAppId { get; set; }
public async Task<IActionResult> ListApps()
{
@ -104,7 +105,7 @@ namespace BTCPayServer.Controllers
StatusMessage = "Error: You are not owner of this store";
return RedirectToAction(nameof(ListApps));
}
var id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32));
var id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20));
using (var ctx = _ContextFactory.CreateContext())
{
var appData = new AppData() { Id = id };
@ -115,7 +116,7 @@ namespace BTCPayServer.Controllers
await ctx.SaveChangesAsync();
}
StatusMessage = "App successfully created";
CreatedAppId = id;
if (appType == AppType.PointOfSale)
return RedirectToAction(nameof(UpdatePointOfSale), new { appId = id });
return RedirectToAction(nameof(ListApps));
@ -136,21 +137,9 @@ namespace BTCPayServer.Controllers
});
}
private async Task<AppData> GetOwnedApp(string appId, AppType? type = null)
private Task<AppData> GetOwnedApp(string appId, AppType? type = null)
{
var userId = GetUserId();
using (var ctx = _ContextFactory.CreateContext())
{
var app = await ctx.UserStore
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
.FirstOrDefaultAsync();
if (app == null)
return null;
if (type != null && type.Value.ToString() != app.AppType)
return null;
return app;
}
return _AppsHelper.GetAppDataIfOwner(GetUserId(), appId, type);
}
private async Task<StoreData[]> GetOwnedStores()

View File

@ -181,5 +181,22 @@ namespace BTCPayServer.Controllers
{
return _Currencies.GetCurrencyData(currency, useFallback);
}
public async Task<AppData> GetAppDataIfOwner(string userId, string appId, AppType? type = null)
{
if (userId == null || appId == null)
return null;
using (var ctx = _ContextFactory.CreateContext())
{
var app = await ctx.UserStore
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
.FirstOrDefaultAsync();
if (app == null)
return null;
if (type != null && type.Value.ToString() != app.AppType)
return null;
return app;
}
}
}
}

View File

@ -57,7 +57,8 @@ namespace BTCPayServer.Controllers
MonitoringDate = invoice.MonitoringExpiration,
OrderId = invoice.OrderId,
BuyerInformation = invoice.BuyerInformation,
Fiat = FormatCurrency((decimal)dto.Price, dto.Currency, _CurrencyNameTable),
Fiat = _CurrencyNameTable.DisplayFormatCurrency((decimal)dto.Price, dto.Currency),
NotificationEmail = invoice.NotificationEmail,
NotificationUrl = invoice.NotificationURL,
RedirectUrl = invoice.RedirectURL,
ProductInformation = invoice.ProductInformation,
@ -228,7 +229,7 @@ namespace BTCPayServer.Controllers
if (!isDefaultCrypto)
return null;
var paymentMethodTemp = invoice.GetPaymentMethods(_NetworkProvider)
.Where(c=> paymentMethodId.CryptoCode == c.GetId().CryptoCode)
.Where(c => paymentMethodId.CryptoCode == c.GetId().CryptoCode)
.FirstOrDefault();
if (paymentMethodTemp == null)
paymentMethodTemp = invoice.GetPaymentMethods(_NetworkProvider).First();
@ -307,7 +308,7 @@ namespace BTCPayServer.Controllers
private string GetDisplayName(PaymentMethodId paymentMethodId, BTCPayNetwork network)
{
return paymentMethodId.PaymentType == PaymentTypes.BTCLike ?
network.DisplayName : network.DisplayName + " (via Lightning)";
network.DisplayName : network.DisplayName + " (Lightning)";
}
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
@ -323,39 +324,12 @@ namespace BTCPayServer.Controllers
if (cryptoCode == productInformation.Currency)
return null;
return FormatCurrency(productInformation.Price, productInformation.Currency, _CurrencyNameTable);
return _CurrencyNameTable.DisplayFormatCurrency(productInformation.Price, productInformation.Currency);
}
private string ExchangeRate(PaymentMethod paymentMethod)
{
string currency = paymentMethod.ParentEntity.ProductInformation.Currency;
return FormatCurrency(paymentMethod.Rate, currency, _CurrencyNameTable);
}
public static string FormatCurrency(decimal price, string currency, CurrencyNameTable currencies)
{
var provider = currencies.GetNumberFormatInfo(currency, true);
var currencyData = currencies.GetCurrencyData(currency, true);
var divisibility = currencyData.Divisibility;
while (true)
{
var rounded = decimal.Round(price, divisibility, MidpointRounding.AwayFromZero);
if ((Math.Abs(rounded - price) / price) < 0.001m)
{
price = rounded;
break;
}
divisibility++;
}
if (divisibility != provider.CurrencyDecimalDigits)
{
provider = (NumberFormatInfo)provider.Clone();
provider.CurrencyDecimalDigits = divisibility;
}
if (currencyData.Crypto)
return price.ToString("C", provider);
else
return price.ToString("C", provider) + $" ({currency})";
return _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate, currency);
}
[HttpGet]
@ -432,23 +406,17 @@ namespace BTCPayServer.Controllers
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50)
{
var model = new InvoicesModel();
var filterString = new SearchString(searchTerm);
foreach (var invoice in await _InvoiceRepository.GetInvoices(new InvoiceQuery()
var model = new InvoicesModel
{
TextSearch = filterString.TextSearch,
Count = count,
SearchTerm = searchTerm,
Skip = skip,
UserId = GetUserId(),
Unusual = !filterString.Filters.ContainsKey("unusual") ? null
: !bool.TryParse(filterString.Filters["unusual"].First(), out var r) ? (bool?)null
: r,
Status = filterString.Filters.ContainsKey("status") ? filterString.Filters["status"].ToArray() : null,
ExceptionStatus = filterString.Filters.ContainsKey("exceptionstatus") ? filterString.Filters["exceptionstatus"].ToArray() : null,
StoreId = filterString.Filters.ContainsKey("storeid") ? filterString.Filters["storeid"].ToArray() : null
}))
Count = count,
StatusMessage = StatusMessage
};
var list = await ListInvoicesProcess(searchTerm, skip, count);
foreach (var invoice in list)
{
model.SearchTerm = searchTerm;
model.Invoices.Add(new InvoiceModel()
{
Status = invoice.Status + (invoice.ExceptionStatus == null ? string.Empty : $" ({invoice.ExceptionStatus})"),
@ -460,12 +428,29 @@ namespace BTCPayServer.Controllers
AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}"
});
}
model.Skip = skip;
model.Count = count;
model.StatusMessage = StatusMessage;
return View(model);
}
private async Task<InvoiceEntity[]> ListInvoicesProcess(string searchTerm = null, int skip = 0, int count = 50)
{
var filterString = new SearchString(searchTerm);
var list = await _InvoiceRepository.GetInvoices(new InvoiceQuery()
{
TextSearch = filterString.TextSearch,
Count = count,
Skip = skip,
UserId = GetUserId(),
Unusual = !filterString.Filters.ContainsKey("unusual") ? null
: !bool.TryParse(filterString.Filters["unusual"].First(), out var r) ? (bool?)null
: r,
Status = filterString.Filters.ContainsKey("status") ? filterString.Filters["status"].ToArray() : null,
ExceptionStatus = filterString.Filters.ContainsKey("exceptionstatus") ? filterString.Filters["exceptionstatus"].ToArray() : null,
StoreId = filterString.Filters.ContainsKey("storeid") ? filterString.Filters["storeid"].ToArray() : null
});
return list;
}
[HttpGet]
[Route("invoices/create")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
@ -528,6 +513,7 @@ namespace BTCPayServer.Controllers
PosData = model.PosData,
OrderId = model.OrderId,
//RedirectURL = redirect + "redirect",
NotificationEmail = model.NotificationEmail,
NotificationURL = model.NotificationUrl,
ItemDesc = model.ItemDesc,
FullNotifications = true,

View File

@ -83,6 +83,7 @@ namespace BTCPayServer.Controllers
entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications;
entity.ExtendedNotifications = invoice.ExtendedNotifications;
entity.NotificationURL = notificationUri?.AbsoluteUri;
entity.NotificationEmail = invoice.NotificationEmail;
entity.BuyerInformation = Map<Invoice, BuyerInformation>(invoice);
entity.PaymentTolerance = storeBlob.PaymentTolerance;
//Another way of passing buyer info to support

View File

@ -51,7 +51,7 @@ namespace BTCPayServer.Controllers
Currency = model.Currency,
ItemDesc = model.CheckoutDesc,
OrderId = model.OrderId,
BuyerEmail = model.NotifyEmail,
NotificationEmail = model.NotifyEmail,
NotificationURL = model.ServerIpn,
RedirectURL = model.BrowserRedirect,
FullNotifications = true

View File

@ -10,23 +10,32 @@ using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using BTCPayServer.Rating;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Authorization;
using BTCPayServer.Authentication;
namespace BTCPayServer.Controllers
{
[Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)]
[AllowAnonymous]
public class RateController : Controller
{
RateFetcher _RateProviderFactory;
BTCPayNetworkProvider _NetworkProvider;
CurrencyNameTable _CurrencyNameTable;
StoreRepository _StoreRepo;
public TokenRepository TokenRepository { get; }
public RateController(
RateFetcher rateProviderFactory,
BTCPayNetworkProvider networkProvider,
TokenRepository tokenRepository,
StoreRepository storeRepo,
CurrencyNameTable currencyNameTable)
{
_RateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
_NetworkProvider = networkProvider;
TokenRepository = tokenRepository;
_StoreRepo = storeRepo;
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
}
@ -36,7 +45,7 @@ namespace BTCPayServer.Controllers
[BitpayAPIConstraint]
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, string storeId)
{
storeId = storeId ?? this.HttpContext.GetStoreData()?.Id;
storeId = await GetStoreId(storeId);
var store = this.HttpContext.GetStoreData();
if (store == null || store.Id != storeId)
store = await _StoreRepo.FindStore(storeId);
@ -66,7 +75,7 @@ namespace BTCPayServer.Controllers
[BitpayAPIConstraint]
public async Task<IActionResult> GetCurrencyPairRate(string baseCurrency, string currency, string storeId)
{
storeId = storeId ?? this.HttpContext.GetStoreData()?.Id;
storeId = await GetStoreId(storeId);
var result = await GetRates2($"{baseCurrency}_{currency}", storeId);
var rates = (result as JsonResult)?.Value as Rate[];
if (rates == null)
@ -79,7 +88,7 @@ namespace BTCPayServer.Controllers
[BitpayAPIConstraint]
public async Task<IActionResult> GetRates(string currencyPairs, string storeId)
{
storeId = storeId ?? this.HttpContext.GetStoreData()?.Id;
storeId = await GetStoreId(storeId);
var result = await GetRates2(currencyPairs, storeId);
var rates = (result as JsonResult)?.Value as Rate[];
if (rates == null)
@ -87,11 +96,29 @@ namespace BTCPayServer.Controllers
return Json(new DataWrapper<Rate[]>(rates));
}
private async Task<string> GetStoreId(string storeId)
{
if (storeId != null && this.HttpContext.GetStoreData()?.Id == storeId)
return storeId;
if(storeId == null)
{
var tokens = await this.TokenRepository.GetTokens(this.User.GetSIN());
storeId = tokens.Select(s => s.StoreId).Where(s => s != null).FirstOrDefault();
}
if (storeId == null)
return null;
var store = await _StoreRepo.FindStore(storeId);
if (store == null)
return null;
this.HttpContext.SetStoreData(store);
return storeId;
}
[Route("api/rates")]
[HttpGet]
public async Task<IActionResult> GetRates2(string currencyPairs, string storeId)
{
storeId = await GetStoreId(storeId);
if (storeId == null)
{
var result = Json(new BitpayErrorsModel() { Error = "You need to specify storeId (in your store settings)" });

View File

@ -99,9 +99,17 @@ namespace BTCPayServer.Controllers
vm.Confirmation = false;
return View(vm);
}
var storeBlob = store.GetStoreBlob();
var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId);
var willBeExcluded = !vm.Enabled;
var showAddress = (vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) || // Testing hint address
(!vm.Confirmation && strategy != null && exisingStrategy != strategy.DerivationStrategyBase.ToString()); // Checking addresses after setting xpub
var showAddress = // Show addresses if:
// - If the user is testing the hint address in confirmation screen
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
// - The user is setting a new derivation scheme
(!vm.Confirmation && strategy != null && exisingStrategy != strategy.DerivationStrategyBase.ToString()) ||
// - The user is clicking on continue without changing anything
(!vm.Confirmation && willBeExcluded == wasExcluded);
if (!showAddress)
{
@ -110,9 +118,7 @@ namespace BTCPayServer.Controllers
if (strategy != null)
await wallet.TrackAsync(strategy.DerivationStrategyBase);
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
var storeBlob = store.GetStoreBlob();
storeBlob.SetExcluded(paymentMethodId, !vm.Enabled);
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
store.SetStoreBlob(storeBlob);
}
catch

View File

@ -32,17 +32,19 @@ namespace BTCPayServer.Controllers
[Route("wallets")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[AutoValidateAntiforgeryToken]
public class WalletsController : Controller
public partial class WalletsController : Controller
{
private StoreRepository _Repo;
private BTCPayNetworkProvider _NetworkProvider;
public StoreRepository Repository { get; }
public BTCPayNetworkProvider NetworkProvider { get; }
public ExplorerClientProvider ExplorerClientProvider { get; }
private readonly UserManager<ApplicationUser> _userManager;
private readonly IOptions<MvcJsonOptions> _mvcJsonOptions;
private readonly NBXplorerDashboard _dashboard;
private readonly ExplorerClientProvider _explorerProvider;
private readonly IFeeProviderFactory _feeRateProvider;
private readonly BTCPayWalletProvider _walletProvider;
RateFetcher _RateProvider;
public RateFetcher RateFetcher { get; }
CurrencyNameTable _currencyTable;
public WalletsController(StoreRepository repo,
CurrencyNameTable currencyTable,
@ -56,13 +58,13 @@ namespace BTCPayServer.Controllers
BTCPayWalletProvider walletProvider)
{
_currencyTable = currencyTable;
_Repo = repo;
_RateProvider = rateProvider;
_NetworkProvider = networkProvider;
Repository = repo;
RateFetcher = rateProvider;
NetworkProvider = networkProvider;
_userManager = userManager;
_mvcJsonOptions = mvcJsonOptions;
_dashboard = dashboard;
_explorerProvider = explorerProvider;
ExplorerClientProvider = explorerProvider;
_feeRateProvider = feeRateProvider;
_walletProvider = walletProvider;
}
@ -70,10 +72,10 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> ListWallets()
{
var wallets = new ListWalletsViewModel();
var stores = await _Repo.GetStoresByUserId(GetUserId());
var stores = await Repository.GetStoresByUserId(GetUserId());
var onChainWallets = stores
.SelectMany(s => s.GetSupportedPaymentMethods(_NetworkProvider)
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
.OfType<DerivationStrategy>()
.Select(d => ((Wallet: _walletProvider.GetWallet(d.Network),
DerivationStrategy: d.DerivationStrategyBase,
@ -111,7 +113,7 @@ namespace BTCPayServer.Controllers
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId)
{
var store = await _Repo.FindStore(walletId.StoreId, GetUserId());
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
DerivationStrategy paymentMethod = GetPaymentMethod(walletId, store);
if (paymentMethod == null)
return NotFound();
@ -120,7 +122,7 @@ namespace BTCPayServer.Controllers
var transactions = await wallet.FetchTransactions(paymentMethod.DerivationStrategyBase);
var model = new ListTransactionsViewModel();
foreach(var tx in transactions.UnconfirmedTransactions.Transactions.Concat(transactions.ConfirmedTransactions.Transactions))
foreach (var tx in transactions.UnconfirmedTransactions.Transactions.Concat(transactions.ConfirmedTransactions.Transactions))
{
var vm = new ListTransactionsViewModel.TransactionViewModel();
model.Transactions.Add(vm);
@ -139,29 +141,33 @@ namespace BTCPayServer.Controllers
[Route("{walletId}/send")]
public async Task<IActionResult> WalletSend(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId)
WalletId walletId, string defaultDestination = null, string defaultAmount = null)
{
if (walletId?.StoreId == null)
return NotFound();
var store = await _Repo.FindStore(walletId.StoreId, GetUserId());
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
DerivationStrategy paymentMethod = GetPaymentMethod(walletId, store);
if (paymentMethod == null)
return NotFound();
var storeData = store.GetStoreBlob();
var rateRules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider);
rateRules.Spread = 0.0m;
var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, GetCurrencyCode(storeData.DefaultLang) ?? "USD");
WalletModel model = new WalletModel();
model.ServerUrl = GetLedgerWebsocketUrl(this.HttpContext, walletId.CryptoCode, paymentMethod.DerivationStrategyBase);
model.CryptoCurrency = walletId.CryptoCode;
WalletModel model = new WalletModel()
{
DefaultAddress = defaultDestination,
DefaultAmount = defaultAmount,
ServerUrl = GetLedgerWebsocketUrl(this.HttpContext, walletId.CryptoCode, paymentMethod.DerivationStrategyBase),
CryptoCurrency = walletId.CryptoCode
};
using (CancellationTokenSource cts = new CancellationTokenSource())
{
try
{
cts.CancelAfter(TimeSpan.FromSeconds(5));
var result = await _RateProvider.FetchRate(currencyPair, rateRules).WithCancellation(cts.Token);
var result = await RateFetcher.FetchRate(currencyPair, rateRules).WithCancellation(cts.Token);
if (result.BidAsk != null)
{
model.Rate = result.BidAsk.Center;
@ -173,7 +179,7 @@ namespace BTCPayServer.Controllers
model.RateError = $"{result.EvaluatedRule} ({string.Join(", ", result.Errors.OfType<object>().ToArray())})";
}
}
catch(Exception ex) { model.RateError = ex.Message; }
catch (Exception ex) { model.RateError = ex.Message; }
}
return View(model);
}
@ -187,7 +193,7 @@ namespace BTCPayServer.Controllers
var ri = new RegionInfo(defaultLang);
return ri.ISOCurrencySymbol;
}
catch(ArgumentException) { }
catch (ArgumentException) { }
return null;
}
@ -197,7 +203,7 @@ namespace BTCPayServer.Controllers
return null;
var paymentMethod = store
.GetSupportedPaymentMethods(_NetworkProvider)
.GetSupportedPaymentMethods(NetworkProvider)
.OfType<DerivationStrategy>()
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode);
return paymentMethod;
@ -257,7 +263,7 @@ namespace BTCPayServer.Controllers
BTCPayNetwork network = null;
if (cryptoCode != null)
{
network = _NetworkProvider.GetNetwork(cryptoCode);
network = NetworkProvider.GetNetwork(cryptoCode);
if (network == null)
throw new FormatException("Invalid value for crypto code");
}
@ -403,7 +409,7 @@ namespace BTCPayServer.Controllers
if (!strategy.Segwit)
{
var parentHashes = usedCoins.Select(c => c.Outpoint.Hash).ToHashSet();
var explorer = _explorerProvider.GetExplorerClient(network);
var explorer = ExplorerClientProvider.GetExplorerClient(network);
var getTransactionAsyncs = parentHashes.Select(h => (Op: explorer.GetTransactionAsync(h), Hash: h)).ToList();
foreach (var getTransactionAsync in getTransactionAsyncs)
{

View File

@ -20,6 +20,7 @@ using BTCPayServer.Events;
using NBXplorer;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Payments;
using BTCPayServer.Services.Mails;
namespace BTCPayServer.HostedServices
{
@ -52,24 +53,44 @@ namespace BTCPayServer.HostedServices
EventAggregator _EventAggregator;
InvoiceRepository _InvoiceRepository;
BTCPayNetworkProvider _NetworkProvider;
IEmailSender _EmailSender;
public InvoiceNotificationManager(
IBackgroundJobClient jobClient,
EventAggregator eventAggregator,
InvoiceRepository invoiceRepository,
BTCPayNetworkProvider networkProvider,
ILogger<InvoiceNotificationManager> logger)
ILogger<InvoiceNotificationManager> logger,
IEmailSender emailSender)
{
Logger = logger as ILogger ?? NullLogger.Instance;
_JobClient = jobClient;
_EventAggregator = eventAggregator;
_InvoiceRepository = invoiceRepository;
_NetworkProvider = networkProvider;
_EmailSender = emailSender;
}
async Task Notify(InvoiceEntity invoice, int? eventCode = null, string name = null)
{
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);
await _EmailSender.SendEmailAsync(
invoice.NotificationEmail, $"BtcPayServer Invoice Notification - ${invoice.StoreId}", emailBody);
}
try
{
if (string.IsNullOrEmpty(invoice.NotificationURL))
@ -203,7 +224,7 @@ namespace BTCPayServer.HostedServices
PaymentTotals = dto.PaymentTotals,
AmountPaid = dto.AmountPaid,
ExchangeRates = dto.ExchangeRates,
};
// We keep backward compatibility with bitpay by passing BTC info to the notification
@ -264,15 +285,15 @@ namespace BTCPayServer.HostedServices
sendRequest()
.ContinueWith(t =>
{
if(t.Status == TaskStatus.RanToCompletion)
{
if (t.Status == TaskStatus.RanToCompletion)
{
completion.TrySetResult(t.Result);
}
if(t.Status == TaskStatus.Faulted)
if (t.Status == TaskStatus.Faulted)
{
completion.TrySetException(t.Exception);
}
if(t.Status == TaskStatus.Canceled)
if (t.Status == TaskStatus.Canceled)
{
completion.TrySetCanceled();
}
@ -289,7 +310,7 @@ namespace BTCPayServer.HostedServices
lock (_SendingRequestsByInvoiceId)
{
_SendingRequestsByInvoiceId.TryGetValue(id, out var executing2);
if(executing2 == sending)
if (executing2 == sending)
_SendingRequestsByInvoiceId.Remove(id);
}
}, TaskScheduler.Default);

View File

@ -83,14 +83,14 @@ namespace BTCPayServer.Hosting
var path = httpContext.Request.Path.Value;
if (
bitpayAuth &&
path == "/invoices" &&
(path == "/invoices" || path == "/invoices/") &&
httpContext.Request.Method == "POST" &&
isJson)
return true;
if (
bitpayAuth &&
path == "/invoices" &&
(path == "/invoices" || path == "/invoices/") &&
httpContext.Request.Method == "GET")
return true;
@ -105,8 +105,8 @@ namespace BTCPayServer.Hosting
return true;
if (
path.Equals("/tokens", StringComparison.Ordinal) &&
( httpContext.Request.Method == "GET" || httpContext.Request.Method == "POST"))
path.Equals("/tokens", StringComparison.Ordinal) &&
(httpContext.Request.Method == "GET" || httpContext.Request.Method == "POST"))
return true;
return false;
@ -140,13 +140,9 @@ namespace BTCPayServer.Hosting
if (reverseProxyScheme != null && _Options.ExternalUrl.Scheme != reverseProxyScheme)
{
if (reverseProxyScheme == "http" && _Options.ExternalUrl.Scheme == "https")
Logs.PayServer.LogWarning($"BTCPay ExternalUrl setting expected to use scheme '{_Options.ExternalUrl.Scheme}' externally, but the reverse proxy uses scheme '{reverseProxyScheme}'");
httpContext.Request.Scheme = reverseProxyScheme;
}
else
{
httpContext.Request.Scheme = _Options.ExternalUrl.Scheme;
Logs.PayServer.LogWarning($"BTCPay ExternalUrl setting expected to use scheme '{_Options.ExternalUrl.Scheme}' externally, but the reverse proxy uses scheme '{reverseProxyScheme}' (X-Forwarded-Port), forcing ExternalUrl");
}
httpContext.Request.Scheme = _Options.ExternalUrl.Scheme;
if (_Options.ExternalUrl.IsDefaultPort)
httpContext.Request.Host = new HostString(_Options.ExternalUrl.Host);
else

View File

@ -53,6 +53,12 @@ namespace BTCPayServer.Models.InvoicingModels
get; set;
}
[EmailAddress]
public string NotificationEmail
{
get; set;
}
[Uri]
public string NotificationUrl
{

View File

@ -142,5 +142,6 @@ namespace BTCPayServer.Models.InvoicingModels
public AddressModel[] Addresses { get; set; }
public DateTimeOffset MonitoringDate { get; internal set; }
public List<Data.InvoiceEventData> Events { get; internal set; }
public string NotificationEmail { get; internal set; }
}
}

View File

@ -15,6 +15,9 @@ namespace BTCPayServer.Models.WalletViewModels
get;
set;
}
public string DefaultAddress { get; set; }
public string DefaultAmount { get; set; }
public decimal? Rate { get; set; }
public int Divisibility { get; set; }
public string Fiat { get; set; }

View File

@ -15,11 +15,14 @@ using System.Collections.Generic;
using System.Collections;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Threading;
using Serilog;
namespace BTCPayServer
{
class Program
{
private const long MAX_DEBUG_LOG_FILE_SIZE = 2000000; // If debug log is in use roll it every N MB.
static void Main(string[] args)
{
ServicePointManager.DefaultConnectionLimit = 100;
@ -31,7 +34,7 @@ namespace BTCPayServer
var logger = loggerFactory.CreateLogger("Configuration");
try
{
// This is the only way toat LoadArgs can print to console. Because LoadArgs is called by the HostBuilder before Logs.Configure is called
// This is the only way that LoadArgs can print to console. Because LoadArgs is called by the HostBuilder before Logs.Configure is called
var conf = new DefaultConfiguration() { Logger = logger }.CreateConfiguration(args);
if (conf == null)
return;
@ -50,6 +53,20 @@ namespace BTCPayServer
l.AddFilter("Microsoft", LogLevel.Error);
l.AddFilter("Microsoft.AspNetCore.Antiforgery.Internal", LogLevel.Critical);
l.AddProvider(new CustomConsoleLogProvider(processor));
// Use Serilog for debug log file.
string debugLogFile = conf.GetOrDefault<string>("debuglog", null);
if (String.IsNullOrEmpty(debugLogFile) == false)
{
Serilog.Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.File(debugLogFile, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: MAX_DEBUG_LOG_FILE_SIZE, rollOnFileSizeLimit: true, retainedFileCountLimit: 1)
.CreateLogger();
l.AddSerilog(Serilog.Log.Logger);
logger.LogDebug($"Debug log file configured for {debugLogFile}.");
}
})
.UseStartup<Startup>()
.Build();
@ -73,6 +90,7 @@ namespace BTCPayServer
Logs.Configuration.LogError("Configuration error");
if (host != null)
host.Dispose();
Serilog.Log.CloseAndFlush();
loggerProvider.Dispose();
}
}

View File

@ -2,10 +2,11 @@
"profiles": {
"Docker-Regtest": {
"commandName": "Project",
"commandLineArgs": "--debuglog debug.log",
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_NETWORK": "regtest",
"BTCPAY_BUNDLEJSCSS": "false",
"BTCPAY_BUNDLEJSCSS": "true",
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
@ -17,4 +18,4 @@
"applicationUrl": "http://127.0.0.1:14142/"
}
}
}
}

View File

@ -77,7 +77,7 @@ namespace BTCPayServer.Rating
if (CurrencyPair.TryParse(id.Identifier.ValueText, out var currencyPair))
{
expression = expression.WithTriviaFrom(expression);
ExpressionsByPair.Add(currencyPair, (expression, id));
ExpressionsByPair.TryAdd(currencyPair, (expression, id));
}
}
base.VisitAssignmentExpression(node);

View File

@ -210,14 +210,14 @@ namespace BTCPayServer.Security
var path = httpContext.Request.Path.Value;
if (
bitpayAuth &&
path == "/invoices" &&
(path == "/invoices" || path == "/invoices/") &&
httpContext.Request.Method == "POST" &&
isJson)
return true;
if (
bitpayAuth &&
path == "/invoices" &&
(path == "/invoices" || path == "/invoices/") &&
httpContext.Request.Method == "GET")
return true;

View File

@ -283,6 +283,11 @@ namespace BTCPayServer.Services.Invoices
get;
set;
}
public string NotificationEmail
{
get;
set;
}
public string NotificationURL
{
get;

View File

@ -29,9 +29,13 @@ namespace BTCPayServer.Services
new Language("pt-PT", "Portuguese"),
new Language("pt-BR", "Portuguese (Brazil)"),
new Language("nl-NL", "Dutch"),
new Language("np-NP", "नेपाली"),
new Language("cs-CZ", "Česky"),
new Language("is-IS", "Íslenska"),
new Language("hr-HR", "Croatian"),
new Language("it-IT", "Italiano"),
new Language("ru-RU", "русский"),
new Language("zh-SP", "中文(简体)"),
};
}
}

View File

@ -101,6 +101,40 @@ namespace BTCPayServer.Services.Rates
currencyProviders.TryAdd(code, number);
}
/// <summary>
/// Format a currency like "0.004 $ (USD)", round to significant divisibility
/// </summary>
/// <param name="value">The value</param>
/// <param name="currency">Currency code</param>
/// <param name="threeLetterSuffix">Add three letter suffix (like USD)</param>
/// <returns></returns>
public string DisplayFormatCurrency(decimal value, string currency, bool threeLetterSuffix = true)
{
var provider = GetNumberFormatInfo(currency, true);
var currencyData = GetCurrencyData(currency, true);
var divisibility = currencyData.Divisibility;
while (true)
{
var rounded = decimal.Round(value, divisibility, MidpointRounding.AwayFromZero);
if ((Math.Abs(rounded - value) / value) < 0.001m)
{
value = rounded;
break;
}
divisibility++;
}
if (divisibility != provider.CurrencyDecimalDigits)
{
provider = (NumberFormatInfo)provider.Clone();
provider.CurrencyDecimalDigits = divisibility;
}
if (currencyData.Crypto)
return value.ToString("C", provider);
else
return value.ToString("C", provider) + $" ({currency})";
}
Dictionary<string, CurrencyData> _Currencies;
static CurrencyData[] LoadCurrency()
@ -135,13 +169,16 @@ namespace BTCPayServer.Services.Rates
foreach (var network in new BTCPayNetworkProvider(NetworkType.Mainnet).GetAll())
{
dico.TryAdd(network.CryptoCode, new CurrencyData()
if (!dico.TryAdd(network.CryptoCode, new CurrencyData()
{
Code = network.CryptoCode,
Divisibility = 8,
Name = network.CryptoCode,
Crypto = true
});
}))
{
dico[network.CryptoCode].Crypto = true;
}
}
return dico.Values.ToArray();
@ -150,9 +187,9 @@ namespace BTCPayServer.Services.Rates
public CurrencyData GetCurrencyData(string currency, bool useFallback)
{
CurrencyData result;
if(!_Currencies.TryGetValue(currency.ToUpperInvariant(), out result))
if (!_Currencies.TryGetValue(currency.ToUpperInvariant(), out result))
{
if(useFallback)
if (useFallback)
{
var usd = GetCurrencyData("USD", false);
result = new CurrencyData()

View File

@ -105,7 +105,6 @@ namespace BTCPayServer.Services.Rates
Providers.Add("cryptopia", new ExchangeSharpRateProvider("cryptopia", new ExchangeCryptopiaAPI(), false));
// Handmade providers
Providers.Add("bitpay", new BitpayRateProvider(new NBitpayClient.Bitpay(new NBitcoin.Key(), new Uri("https://bitpay.com/"))));
Providers.Add(QuadrigacxRateProvider.QuadrigacxName, new QuadrigacxRateProvider());
Providers.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, HttpClient = _httpClientFactory?.CreateClient(), Authenticator = _CoinAverageSettings });
Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient() });

View File

@ -21,7 +21,7 @@
<h1 class="mb-4">@Model.Title</h1>
<form method="post" asp-antiforgery="false">
<div class="row">
@for(int i = 0; i < Model.Items.Length; i++)
@for (int i = 0; i < Model.Items.Length; i++)
{
var className = (Model.Items.Length - i) > (Model.Items.Length % 3) ? "col-sm-4 mb-3" : "col align-self-center";
var item = Model.Items[i];
@ -32,10 +32,11 @@
}
</div>
</form>
@if(Model.ShowCustomAmount)
@if (Model.ShowCustomAmount)
{
<div class="row mt-4">
<div class="col-md-4 offset-md-4 col-sm-6 offset-sm-3">
<div class="col-sm-3">&nbsp;</div>
<div class="col-sm-6">
<form method="post" asp-antiforgery="false" data-buy>
<div class="input-group">
<input class="form-control" type="number" min="0" step="@Model.Step" name="amount" placeholder="amount"><div class="input-group-append">
@ -44,6 +45,7 @@
</div>
</form>
</div>
<div class="col-sm-3">&nbsp;</div>
</div>
}
</div>

View File

@ -57,14 +57,14 @@
<div class="container text-center">
<h2>Video tutorials</h2>
<div class="row">
<div class="col-md-4 text-center">
<div class="col-md-2 text-center">
</div>
<div class="col-md-4 text-center">
<div class="col-md-8 text-center">
<a href="https://www.youtube.com/channel/UCpG9WL6TJuoNfFVkaDMp9ug" target="_blank">
<img src="~/img/youtube.png" height="225" width="400" />
<img src="~/img/youtube.png" class="img-fluid" />
</a>
</div>
<div class="col-md-6 text-center">
<div class="col-md-2 text-center">
</div>
</div>
</div>

View File

@ -188,9 +188,21 @@
</form>
</div>
<div class="bp-view payment scan" id="scan">
<div class="wrapBtnGroup" v-bind:class="{ invisible: lndModel === null }">
<div class="btnGroupLnd">
<button onclick="lndToggleBolt11()" v-bind:class="{ active: lndModel != null && lndModel.toggle === 0 }"
v-bind:title="$t('BOLT 11 Invoice')">
{{$t("BOLT 11 Invoice")}}
</button>
<button onclick="lndToggleNode()" v-bind:class="{ active: lndModel != null && lndModel.toggle === 1 }"
v-bind:title="$t('Node Info')">
{{$t("Node Info")}}
</button>
</div>
</div>
<div class="payment__scan">
<img v-bind:src="srvModel.cryptoImage" class="qr_currency_icon" />
<qrcode v-bind:val="srvModel.invoiceBitcoinUrlQR" v-bind:size="256" bg-color="#f5f5f7" fg-color="#000">
<qrcode v-bind:val="scanDisplayQr" v-bind:size="256" bg-color="#f5f5f7" fg-color="#000">
</qrcode>
</div>
<div class="payment__details__instruction__open-wallet">

View File

@ -70,7 +70,7 @@
{{$t("nested.lang")}} >>
*@
<select class="cmblang reverse invisible" onchange="changeLanguage($(this).val())">
@foreach(var lang in langService.GetLanguages())
@foreach (var lang in langService.GetLanguages())
{
<option value="@lang.Code">@lang.DisplayName</option>
}
@ -117,9 +117,13 @@
'pt': { translation: locales_pt },
'pt-BR': { translation: locales_pt_br },
'nl': { translation: locales_nl },
'np': { translation: locales_np },
'cs-CZ': { translation: locales_cs },
'is-IS': { translation: locales_is },
'hr-HR': { translation: locales_hr }
'it-IT': { translation: locales_it },
'hr-HR': { translation: locales_hr },
'ru-RU': { translation: locales_ru },
'zh-SP': { translation: locales_zh_sp }
},
});
@ -151,6 +155,8 @@
},
data: {
srvModel: srvModel,
lndModel: null,
scanDisplayQr: "",
expiringSoon: false
}
});

View File

@ -44,6 +44,11 @@
<input asp-for="BuyerEmail" class="form-control" />
<span asp-validation-for="BuyerEmail" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NotificationEmail" class="control-label"></label>
<input asp-for="NotificationEmail" class="form-control" />
<span asp-validation-for="NotificationEmail" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NotificationUrl" class="control-label"></label>
<input asp-for="NotificationUrl" class="form-control" />

View File

@ -87,6 +87,10 @@
<th>Total fiat due</th>
<td>@Model.Fiat</td>
</tr>
<tr>
<th>Notification Email</th>
<td>@Model.NotificationEmail</td>
</tr>
<tr>
<th>Notification Url</th>
<td>@Model.NotificationUrl</td>
@ -167,14 +171,14 @@
<th class="text-right">Rate</th>
<th class="text-right">Paid</th>
<th class="text-right">Due</th>
@if(Model.StatusException == "paidOver")
@if (Model.StatusException == "paidOver")
{
<th class="text-right">Overpaid</th>
}
</tr>
</thead>
<tbody>
@foreach(var payment in Model.CryptoPayments)
@foreach (var payment in Model.CryptoPayments)
{
<tr>
<td>@payment.PaymentMethod</td>
@ -182,7 +186,7 @@
<td class="text-right">@payment.Rate</td>
<td class="text-right">@payment.Paid</td>
<td class="text-right">@payment.Due</td>
@if(Model.StatusException == "paidOver")
@if (Model.StatusException == "paidOver")
{
<td class="text-right">@payment.Overpaid</td>
}
@ -192,7 +196,7 @@
</table>
</div>
</div>
@if(Model.OnChainPayments.Count > 0)
@if (Model.OnChainPayments.Count > 0)
{
<div class="row">
<div class="col-md-12">
@ -207,7 +211,7 @@
</tr>
</thead>
<tbody>
@foreach(var payment in Model.OnChainPayments)
@foreach (var payment in Model.OnChainPayments)
{
var replaced = payment.Replaced ? "class='linethrough'" : "";
<tr @replaced>
@ -226,7 +230,7 @@
</div>
</div>
}
@if(Model.OffChainPayments.Count > 0)
@if (Model.OffChainPayments.Count > 0)
{
<div class="row">
<div class="col-md-12">
@ -239,7 +243,7 @@
</tr>
</thead>
<tbody>
@foreach(var payment in Model.OffChainPayments)
@foreach (var payment in Model.OffChainPayments)
{
<tr>
<td>@payment.Crypto</td>
@ -262,7 +266,7 @@
</tr>
</thead>
<tbody>
@foreach(var address in Model.Addresses)
@foreach (var address in Model.Addresses)
{
var current = address.Current ? "font-weight-bold" : "";
<tr>
@ -286,7 +290,7 @@
</tr>
</thead>
<tbody>
@foreach(var evt in Model.Events)
@foreach (var evt in Model.Events)
{
<tr>
<td>@evt.Timestamp.ToBrowserDate()</td>

View File

@ -32,10 +32,19 @@
If you want all confirmed and complete invoices, you can duplicate a filter <code>status:confirmed status:complete</code>.
</p>
</div>
</div>
</div>
<div class="row no-gutter" style="margin-bottom: 5px;">
<div class="col-lg-4">
<a asp-action="CreateInvoice" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new invoice</a>
</div>
<div class="col-lg-8">
<div class="form-group">
<form asp-action="SearchInvoice" method="post">
<form asp-action="SearchInvoice" method="post" style="float:right;">
<div class="input-group">
<input asp-for="SearchTerm" class="form-control" />
<input asp-for="SearchTerm" class="form-control" style="width:300px;" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary" title="Search invoice">
<span class="fa fa-search"></span> Search
@ -50,7 +59,6 @@
</div>
<div class="row">
<a asp-action="CreateInvoice" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new invoice</a>
<table class="table table-sm table-responsive-md">
<thead>
<tr>
@ -63,12 +71,12 @@
</tr>
</thead>
<tbody>
@foreach(var invoice in Model.Invoices)
@foreach (var invoice in Model.Invoices)
{
<tr>
<td>@invoice.Date.ToTimeAgo()</td>
<td>
@if(invoice.RedirectUrl != string.Empty)
@if (invoice.RedirectUrl != string.Empty)
{
<a href="@invoice.RedirectUrl">@invoice.OrderId</a>
}
@ -78,7 +86,7 @@
}
</td>
<td>@invoice.InvoiceId</td>
@if(invoice.Status == "paid")
@if (invoice.Status == "paid")
{
<td>
<div class="btn-group">
@ -97,7 +105,7 @@
}
<td style="text-align:right">@invoice.AmountCurrency</td>
<td style="text-align:right">
@if(invoice.ShowCheckout)
@if (invoice.ShowCheckout)
{
<a asp-action="Checkout" asp-route-invoiceId="@invoice.InvoiceId">Checkout</a> <span>-</span>
}<a asp-action="Invoice" asp-route-invoiceId="@invoice.InvoiceId">Details</a>
@ -107,7 +115,7 @@
</tbody>
</table>
<span>
@if(Model.Skip != 0)
@if (Model.Skip != 0)
{
<a href="@Url.Action("ListInvoices", new
{

View File

@ -56,5 +56,6 @@
width: 150,
height: 150
});
$("#qrCode > img").css({ "margin": "auto" });
</script>
}

View File

@ -11,6 +11,8 @@ namespace BTCPayServer
static readonly Regex _WalletStoreRegex = new Regex("^S-([a-zA-Z0-9]{30,60})-([a-zA-Z]{2,5})$");
public static bool TryParse(string str, out WalletId walletId)
{
if (str == null)
throw new ArgumentNullException(nameof(str));
walletId = null;
WalletId w = new WalletId();
var match = _WalletStoreRegex.Match(str);
@ -32,8 +34,37 @@ namespace BTCPayServer
}
public string StoreId { get; set; }
public string CryptoCode { get; set; }
public override bool Equals(object obj)
{
WalletId item = obj as WalletId;
if (item == null)
return false;
return ToString().Equals(item.ToString(), StringComparison.InvariantCulture);
}
public static bool operator ==(WalletId a, WalletId b)
{
if (System.Object.ReferenceEquals(a, b))
return true;
if (((object)a == null) || ((object)b == null))
return false;
return a.ToString() == b.ToString();
}
public static bool operator !=(WalletId a, WalletId b)
{
return !(a == b);
}
public override int GetHashCode()
{
return ToString().GetHashCode(StringComparison.Ordinal);
}
public override string ToString()
{
if (StoreId == null || CryptoCode == null)
return "";
return $"S-{StoreId}-{CryptoCode.ToUpperInvariant()}";
}
}

View File

@ -9236,7 +9236,7 @@ strong {
}
.bp-view.scan {
padding-top: .9em !important;
padding-top: 0em !important;
}
.bp-view:not(.active) {
@ -9376,11 +9376,67 @@ strong {
border-bottom: 1px dotted;
}
.wrapBtnGroup {
text-align: center;
margin: -2px 0px 12px;
padding: 0 15px;
}
.btnGroupLnd {
margin: 0 auto;
text-align: center;
width: inherit;
display: table;
background-color: #fff;
color: #000;
font-size: 0px;
width:100%;
}
.btnGroupLnd button:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
border-right: 0px;
}
.btnGroupLnd button:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.btnGroupLnd button {
display: table-cell;
border: solid 1px #24725B;
padding: 6px 9px;
margin: 0px;
font-size: 12px;
width: 50%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.btnGroupLnd button:hover {
background-color: #eeffee;
}
.btnGroupLnd button.active {
background-color: #329F80;
border-color: #329F80;
color: #fff;
}
.btnGroupLnd button.active:hover {
background-color: #24725B;
border-color: #24725B;
color: #fff;
}
.payment__details__instruction__open-wallet {
display: block;
margin-left: auto;
margin-right: auto;
margin: 15px 15px 18px;
margin: 0px 15px 18px;
text-align: center;
}

View File

@ -54,6 +54,7 @@
.clickable_underline {
border-bottom: 1px dotted #ccc;
font-size: 13px;
}
.payment__currencies:hover .clickable_underline {

View File

@ -20,6 +20,17 @@ function resetTabsSlider() {
closePaymentMethodDialog(null);
}
function changeCurrency(currency) {
if (currency !== null && srvModel.paymentMethodId !== currency) {
$(".payment__currencies").hide();
$(".payment__spinner").show();
checkoutCtrl.scanDisplayQr = "";
srvModel.paymentMethodId = currency;
fetchStatus();
}
return false;
}
function onDataCallback(jsonData) {
// extender properties used
jsonData.shapeshiftUrl = "https://shapeshift.io/shifty.html?destination=" + jsonData.btcAddress + "&output=" + jsonData.paymentMethodId + "&amount=" + jsonData.btcDue;
@ -61,25 +72,32 @@ function onDataCallback(jsonData) {
}
// restoring qr code view only when currency is switched
if (jsonData.paymentMethodId === srvModel.paymentMethodId &&
checkoutCtrl.scanDisplayQr === "") {
checkoutCtrl.scanDisplayQr = jsonData.invoiceBitcoinUrlQR;
}
if (jsonData.paymentMethodId === srvModel.paymentMethodId) {
$(".payment__currencies").show();
$(".payment__spinner").hide();
}
if (jsonData.isLightning && checkoutCtrl.lndModel === null) {
var lndModel = {
toggle: 0
};
checkoutCtrl.lndModel = lndModel;
}
if (!jsonData.isLightning) {
checkoutCtrl.lndModel = null;
}
// updating ui
checkoutCtrl.srvModel = jsonData;
}
function changeCurrency(currency) {
if (currency !== null && srvModel.paymentMethodId !== currency) {
$(".payment__currencies").hide();
$(".payment__spinner").show();
srvModel.paymentMethodId = currency;
fetchStatus();
}
return false;
}
function fetchStatus() {
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/" + srvModel.paymentMethodId + "/status";
$.ajax({
@ -93,6 +111,16 @@ function fetchStatus() {
});
}
function lndToggleBolt11() {
checkoutCtrl.lndModel.toggle = 0;
checkoutCtrl.scanDisplayQr = checkoutCtrl.srvModel.invoiceBitcoinUrlQR;
}
function lndToggleNode() {
checkoutCtrl.lndModel.toggle = 1;
checkoutCtrl.scanDisplayQr = checkoutCtrl.srvModel.peerInfo;
}
// private methods
$(document).ready(function () {
// initialize
@ -117,7 +145,7 @@ $(document).ready(function () {
progressStart(srvModel.maxTimeSeconds); // Progress bar
if (srvModel.requiresRefundEmail && !validateEmail(srvModel.customerEmail))
emailForm(); // Email form Display
showEmailForm();
else
hideEmailForm();
}
@ -133,7 +161,7 @@ $(document).ready(function () {
}
// Email Form
// Setup Email mode
function emailForm() {
function showEmailForm() {
$(".modal-dialog").addClass("enter-purchaser-email");
$("#emailAddressForm .action-button").click(function () {

View File

@ -8,11 +8,11 @@ const locales_fr = {
"Contact_Body": "Merci de renseigner l'adresse email ci-dessous. Nous vous contacterons à cette adresse si il y a un problème avec votre paiement.",
"Your email": "Votre email",
"Continue": "Continuer",
"Please enter a valid email address": "Merci de rentrer une addrese email valide",
"Please enter a valid email address": "Merci de saisir une addrese email valide",
"Order Amount": "Montant de la commande",
"Network Cost": "Coût réseau",
"Already Paid": "Déjà payé",
"Due": "Dûe",
"Due": "Reste à payer",
// Tabs
"Scan": "Scanner",
"Copy": "Copier",
@ -25,18 +25,18 @@ const locales_fr = {
"Address": "Adresse",
"Copied": "Copié",
// Conversion tab
"ConversionTab_BodyTop": "Vous pouvez payer {{btcDue}} {{cryptoCode}} en utilisant d'autre crypto-monnaies alternatives non supportées directement par le marchant.",
"ConversionTab_BodyDesc": "Ce service est fournis par un tiers partie. Cependant, nous n'avons aucun controle la façon dont sera traité vos fonds. La facture sera considérée payée seulement quand les fonds seront reçus sur la blockchain {{ cryptoCode }}.",
"Shapeshift_Button_Text": "Payer avec une crypto-monnaie alternative",
"ConversionTab_Lightning": "Pas de fournisseur disponible pour les paiements sur le Lightning Network.",
"ConversionTab_BodyTop": "Vous pouvez payer {{btcDue}} {{cryptoCode}} en utilisant d'autres crypto-monnaies alternatives non supportées directement par le marchand.",
"ConversionTab_BodyDesc": "Ce service est fourni par un tiers. Nous n'avons aucun contrôle sur la façon dont seront traités vos fonds. La facture sera considérée payée seulement quand les fonds seront reçus sur la blockchain {{ cryptoCode }}.",
"Shapeshift_Button_Text": "Payer en altcoins",
"ConversionTab_Lightning": "Le service de conversion n'est pas disponible pour les paiements sur le Lightning Network.",
// Invoice expired
"Invoice expiring soon...": "La facture va bientôt expirer...",
"Invoice expired": "Facture expirée",
"What happened?": "Que s'est t'il passé?",
"InvoiceExpired_Body_1": "La facture a expirée. Une facture est seulement valide pour {{maxTimeMinutes}} minutes. \
Vous pouvez revenir sur {{storeName}} si vous voulez resoumettre votre paiement.",
"InvoiceExpired_Body_2": "Si vous avez essayé d'envoyer un paiement, il n'a pas encore été accepté par la blockchain. Nous n'avons pas encore reçu vos fonds.",
"InvoiceExpired_Body_3": "Si votre transaction n'a pas été accepté par la blockchain, vos fonds reviendront dans votre portefueille. Selon votre portefueille, cela peut prendre entre 48 et 72 heures.",
"What happened?": "Que s'est-il passé ?",
"InvoiceExpired_Body_1": "La facture a expiré. Une facture est valide seulement {{maxTimeMinutes}} minutes. \
Si vous voulez soumettre à nouveau votre paiement, vous pouvez revenir sur {{storeName}} .",
"InvoiceExpired_Body_2": "Si vous avez envoyé un paiement, ce dernier n'a pas encore été inscrit dans la blockchain. Nous n'avons pas reçu vos fonds.",
"InvoiceExpired_Body_3": "Si votre transaction n'est pas inscrite dans la blockchain, vos fonds reviendront dans votre portefeuille. Selon votre portefeuille, cela peut prendre entre 48 et 72 heures.",
"Invoice ID": "Numéro de facture",
"Order ID": "Numéro de commande",
"Return to StoreName": "Retourner sur {{storeName}}",
@ -44,10 +44,10 @@ Vous pouvez revenir sur {{storeName}} si vous voulez resoumettre votre paiement.
"This invoice has been paid": "Cette facture a été payée",
// Invoice archived
"This invoice has been archived": "Cette facture a été archivée",
"Archived_Body": "Merci de contacter le marchand pour plus d'assistance ou d'information sur cette commande.",
"Archived_Body": "Merci de contacter le marchand pour obtenir de l'aide ou des informations sur cette commande.",
// Lightning
"BOLT 11 Invoice": "Facture BOLT 11",
"Node Info": "Information du noeud",
"Node Info": "Informations sur le nœud",
//
"txCount": "{{count}} transaction",
"txCount_plural": "{{count}} transactions"

View File

@ -0,0 +1,54 @@
const locales_it = {
nested: {
lang: 'Lingua'
},
"Awaiting Payment...": "In attesa del Pagamento...",
"Pay with": "Paga con",
"Contact and Refund Email": "Email di Contatto e Rimborso",
"Contact_Body": "Inserisci un indirizzo email qui sotto. Ti contatteremo a questo indirizzo in caso di problemi con il pagamento.",
"Your email": "La tua email",
"Continue": "Continua",
"Please enter a valid email address": "Inserisci un indirizzo email valido",
"Order Amount": "Importo dell'Ordine",
"Network Cost": "Costi di Rete",
"Already Paid": "Già pagato",
"Due": "Dovuto",
// Tabs
"Scan": "Scansiona",
"Copy": "Copia",
"Conversion": "Conversione",
// Scan tab
"Open in wallet": "Apri nel portafoglio",
// Copy tab
"CompletePay_Body": "Per completare il pagamento, inviare {{btcDue}} {{cryptoCode}} all'indirizzo riportato di seguito.",
"Amount": "Importo",
"Address": "Indirizzo",
"Copied": "Copiato",
// Conversion tab
"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.",
"Shapeshift_Button_Text": "Paga con Altcoin",
"ConversionTab_Lightning": "Nessun fornitore di conversione disponibile per i pagamenti Lightning Network.",
// Invoice expired
"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. \
Puoi 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": "Se la transazione non viene accettata dalla rete, i fondi saranno nuovamente spendibili nel tuo portafoglio. A seconda del portafoglio, potrebbero essere necessarie 48-72 ore.",
"Invoice ID": "Numero della Fattura",
"Order ID": "Numero dell'Ordine",
"Return to StoreName": "Ritorna a {{storeName}}",
// Invoice paid
"This invoice has been paid": "La fattura è stata pagata",
// Invoice archived
"This invoice has been archived": "TQuesta fattura è stata pagata",
"Archived_Body": "Contatta il negozio per informazioni sull'ordine o per assistenza",
// Lightning
"BOLT 11 Invoice": "Fattura BOLT 11",
"Node Info": "Informazioni sul Nodo",
//
"txCount": "{{count}} transazione",
"txCount_plural": "{{count}} transazioni"
};

View File

@ -0,0 +1,55 @@
const locales_np = {
nested: {
lang: 'भाषा'
},
"Awaiting Payment...": "भुक्तानी पर्खँदै...",
"Pay with": "तिर्नुहोस्",
"Contact and Refund Email": "सम्पर्क र फिर्ता इमेल",
"Contact_Body": "कृपया तल ईमेल ठेगाना प्रदान गर्नुहोस्। तपाईंको भुक्तानीको साथमा समस्या छ भने हामी यो ठेगानामा सम्पर्क गर्नेछौं",
"Your email": "तिम्रो इमेल",
"Continue": "जारी राख्नुहोस्",
"Please enter a valid email address": "कृपया वैध ईमेल ठेगाना प्रविष्ट गर्नुहोस्",
"Order Amount": "अर्डर रकम",
"Network Cost": "नेटवर्क लागत",
"Already Paid": "तिरिसकेको",
"Due": "बाँकी",
// Tabs
"Scan": "स्क्यान गर्नुहोस्",
"Copy": "कापी",
"Conversion": "रूपान्तरण",
// Scan tab
"Open in wallet": "वालेटमा खोल्नुहोस्",
// Copy tab
"CompletePay_Body": "आफ्नो भुक्तानी पूरा गर्न, कृपया तल ठेगानामा {{btcDue}} {{cryptoCode}} पठाउनुहोस्",
"Amount": "राशि",
"Address": "ठेगाना",
"Copied": "प्रतिलिपि गरियो",
// Conversion tab
"ConversionTab_BodyTop": "तपाईं एक व्यापारी को सीधा समर्थन भन्दा Altcoins अन्य को उपयोग को {{btcDue}} {{cryptoCode}} भुगतान गर्न सक्छन्",
"ConversionTab_BodyDesc": "यो सेवा तेस्रो पार्टी द्वारा प्रदान गरिएको छ। कृपया ध्यान राख्नुहोस् कि हामीसँग कुनै नियन्त्रण छैन कि कसरी प्रदायकहरूले तपाईंको रकम अगाडि बढ्नेछन्। रकम प्राप्त {{cryptoCode}} Blockchain भएको बेलामा इनभ्वाइस मात्र भुक्तानी चिन्ह लगाइनेछ",
"Shapeshift_Button_Text": "तिर्नुहोस् Altcoins",
"ConversionTab_Lightning": "Lightning Network भुक्तानीको लागि कुनै परिवर्तन प्रदायकहरू उपलब्ध छैनन्",
// Invoice expired
"Invoice expiring soon...": "चलानीको म्याद सकियो",
"Invoice expired": "इनभ्वाइस समाप्त भयो",
"What happened?": "के भयो",
"InvoiceExpired_Body_1": "यो चलानी समाप्त भएको छ। इनभ्वाइस {{maxTimeMinutes}} मिनेटको लागि मात्र वैध छ। \
तपाइँ आफ्नो भुक्तानी पुन: पेश गर्न चाहानुहुन्छ यदि तपाइँ {{ storeName }} भुक्तानी पठाउन प्रयास गर्नुभयो भने।",
"InvoiceExpired_Body_2": "भने यो अझै नेटवर्कद्वारा स्वीकार गरिएको छैन। हामीले अझै सम्म तपाईंको रकम प्राप्त गरेका छैनौ।",
"InvoiceExpired_Body_3": "यदि लेनदेन नेटवर्क द्वारा स्वीकार गरेन भने, रकम तपाईंको वालेटमा फेरि खर्चयोग्य हुनेछ। तपाईंको वालेटको आधारमा, यो 48-72 घण्टा लाग्न सक्छ",
"Invoice ID": "चलानी आईडी",
"Order ID": "अर्डर आईडी",
"Return to StoreName": "यसलाई फर्काउनुहोस् {{storeName}}",
// Invoice paid
"This invoice has been paid": "इनभ्वाइस भुक्तानी गरिएको छ",
// Invoice archived
"This invoice has been archived": "इनभ्वाइस संग्रहीत गरिएको छ",
"Archived_Body": "कृपया स्टोर जानकारी वा सहयोगको लागि स्टोरलाई सम्पर्क गर्नुहोस्",
// Lightning
"BOLT 11 Invoice": "BOLT 11 इनभ्वाइस",
"Node Info": "नोड जानकारी",
//
"txCount": "{{count}} लेनदेन",
"txCount_plural": "{{count}} लेनदेन"
};

View File

@ -0,0 +1,54 @@
const locales_ru = {
nested: {
lang: 'язык'
},
"Awaiting Payment...": "Ожидание оплаты...",
"Pay with": "заплатить",
"Contact and Refund Email": "Контактный адрес электронной почты",
"Contact_Body": "Укажите ниже адрес электронной почты. Мы свяжемся с вами по этому адресу, если у вас возникла проблема с оплатой.",
"Your email": "Ваш адрес электронной почты",
"Continue": "Продолжить",
"Please enter a valid email address": "Пожалуйста, введите действующий адрес электронной почты",
"Order Amount": "Сумма заказа",
"Network Cost": "Ценность сети",
"Already Paid": "Уже оплачено",
"Due": "является обязательной",
// Tabs
"Scan": "просканировать",
"Copy": "Скопировать",
"Conversion": "Конвертация",
// Scan tab
"Open in wallet": "Открыть кошелек",
// Copy tab
"CompletePay_Body": "Чтобы завершить оплату, отправьте {{btcDue}} {{cryptoCode}} по адресу, указанному ниже.",
"Amount": "Сумма",
"Address": "Адрес",
"Copied": "Скопировано",
// Conversion tab
"ConversionTab_BodyTop": "Вы можете заплатить {{btcDue}} {{cryptoCode}} используя Altcoins отличные от предпочитаемых продавцом.",
"ConversionTab_BodyDesc": "Эта услуга предоставляется третьим лицом. Пожалуйста, имейте в виду, что мы не контролируем, каким образом провайдеры переедут ваши фондовые средства. Счет будет закрыт только после {{cryptoCode}} Blockchain получения средств.",
"Shapeshift_Button_Text": "заплатить Altcoins",
"ConversionTab_Lightning": "Возможность конвертации Lightning Network платежей отсутствует.",
// Invoice expired
"Invoice expiring soon...": "Время выставленного счета вскоре истечёт...",
"Invoice expired": "Срок действия выставленного счета истек",
"What happened?": "Что случилось?",
"InvoiceExpired_Body_1": "Срок действия этого счета истек. Счет действителен только {{maxTimeMinutes}} минут. \
вы можете посетить {{storeName}} опять, если захотите оплатить.",
"InvoiceExpired_Body_2": "ваша оплата пока еще не принята системой. Мы еще не получили ваши фонды.",
"InvoiceExpired_Body_3": "если транзакция не пройдёт Ваши фонды будут возвращены в ваш кошелек.в зависимости от Вашего кошелек это может занять от 48 до 72 часов.",
"Invoice ID": "Порядковый номер выставленного счета",
"Order ID": "Порядковый номер заказа",
"Return to StoreName": "возвратить в {{storeName}}",
// Invoice paid
"This invoice has been paid": "Этот выставленный счет был оплачен",
// Invoice archived
"This invoice has been archived": "Этот счет был заархивирован.",
"Archived_Body": "ожалуйста, обратитесь в магазин для получения информации о заказе или помощи",
// Lightning
"BOLT 11 Invoice": "счет BOLT 11",
"Node Info": "Информация об устройстве",
//
"txCount": "{{count}} Сделка",
"txCount_plural": "{{count}} операции"
};

View File

@ -0,0 +1,54 @@
const locales_zh_sp = {
nested: {
lang: '语言'
},
"Awaiting Payment...": "等待付款中...",
"Pay with": "支付方式",
"Contact and Refund Email": "联络和退款邮箱",
"Contact_Body": "请输入您的邮箱地址。如果您的支付出现问题,我们将会通过此邮箱地址与您联系。",
"Your email": "您的邮箱",
"Continue": "继续",
"Please enter a valid email address": "请输入有效的邮箱地址",
"Order Amount": "订单总额",
"Network Cost": "网络费用",
"Already Paid": "已支付",
"Due": "待支付",
// Tabs
"Scan": "扫一扫",
"Copy": "复制",
"Conversion": "兑换支付",
// Scan tab
"Open in wallet": "在钱包中打开",
// Copy tab
"CompletePay_Body": "请发送{{btcDue}} {{cryptoCode}}至下方地址以完成支付。",
"Amount": "金额",
"Address": "地址",
"Copied": "已复制",
// Conversion tab
"ConversionTab_BodyTop": "您也可以使用商家支持以外的其他altcoins支付{{btcDue}} {{cryptoCode}}。",
"ConversionTab_BodyDesc": "请您注意,这项服务由第三方应用提供,所以我们无法直接获悉和操控第三方应用的具体兑换过程。订单只有在支付款被{{cryptocode}}的区块链接收之后,才会显示已支付。",
"Shapeshift_Button_Text": "Altcoins支付",
"ConversionTab_Lightning": "闪电网络不支持其他altcoins兑换支付。",
// Invoice expired
"Invoice expiring soon...": "订单即将过期...",
"Invoice expired": "订单已过期 ",
"What happened?": "发生了什么?",
"InvoiceExpired_Body_1": "您的订单已过期。一个订单只在{{maxTimeMinutes}}分钟内有效。 \
如果您想要再次支付,可返回{{storeName}}。",
"InvoiceExpired_Body_2": "如果您已支付,但付款没有被网络确认接收。这说明我方目前还未收到您的支付款。",
"InvoiceExpired_Body_3": "如果您的支付最终被网络拒绝支付款将被返回您的钱包。根据您使用的钱包不同可能需要48-72小时。",
"Invoice ID": "订单号",
"Order ID": "订货号",
"Return to StoreName": "返回{{storeName}}。",
// Invoice paid
"This invoice has been paid": "此订单已支付",
// Invoice archived
"This invoice has been archived": "此订单已归档",
"Archived_Body": "请联系商家获得更多的订单信息或帮助",
// Lightning
"BOLT 11 Invoice": "BOLT 11 订单",
"Node Info": "节点信息(Node Info)",
//
"txCount": "{{count}}笔交易",
"txCount_plural": "{{count}}笔交易"
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -64,7 +64,7 @@
else {
bridge.sendCommand('test', null, 5)
.catch(function (reason) {
if (reason.message === "Sign failed")
if (reason.name === "TransportError")
reason = "Have you forgot to activate browser support in your ledger app?";
Write('hw', 'error', reason);
})

View File

@ -15,7 +15,12 @@ $(function () {
var recommendedFees = "";
var recommendedBalance = "";
var cryptoCode = $("#cryptoCode").val();
if (srvModel.defaultAddress !== null) {
$("#destination-textbox").val(srvModel.defaultAddress);
}
if (srvModel.defaultAmount !== null) {
$("#amount-textbox").val(srvModel.defaultAmount);
}
function WriteAlert(type, message) {
$("#walletAlert").removeClass("alert-danger");
$("#walletAlert").removeClass("alert-warning");
@ -31,9 +36,9 @@ $(function () {
$("#" + prefix + "-error").css("display", "none");
$("#" + prefix + "-success").css("display", "none");
$("#" + prefix+"-" + type).css("display", "block");
$("#" + prefix + "-" + type).css("display", "block");
$("." + prefix +"-label").text(message);
$("." + prefix + "-label").text(message);
}
$("#sendform").on("submit", function (elem) {
@ -61,7 +66,7 @@ $(function () {
confirmButton.prop("disabled", true);
confirmButton.addClass("disabled");
bridge.sendCommand('sendtoaddress', args, 60 * 5 /* timeout */)
bridge.sendCommand('sendtoaddress', args, 60 * 10 /* timeout */)
.catch(function (reason) {
WriteAlert("danger", reason);
confirmButton.prop("disabled", false);
@ -133,9 +138,8 @@ $(function () {
}
else {
bridge.sendCommand('test', null, 5)
.catch(function (reason)
{
if (reason.message === "Sign failed")
.catch(function (reason) {
if (reason.name === "TransportError")
reason = "Are you running the ledger app with version equals or above 1.2.4?";
Write('hw', 'error', reason);
})

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
FROM microsoft/dotnet:2.1.300-sdk-alpine3.7 AS builder
FROM microsoft/dotnet:2.1.402-sdk-alpine3.7 AS builder
WORKDIR /source
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer.csproj
# Cache some dependencies
@ -6,7 +6,7 @@ RUN dotnet restore
COPY BTCPayServer/. .
RUN dotnet publish --output /app/ --configuration Release
FROM microsoft/dotnet:2.1.0-aspnetcore-runtime-alpine3.7
FROM microsoft/dotnet:2.1.4-aspnetcore-runtime-alpine3.7
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
RUN apk add --no-cache icu-libs

View File

@ -10,7 +10,7 @@
BTCPay Server is a free and open-source cryptocurrency payment processor which allows you to receive payments in Bitcoin and altcoins directly, with no fees, transaction cost or a middleman.
BTCPay is a non-custodial invoicing system which eliminates the involvement of a third-party. Payments with BTCPay go directly to your wallet, which increases the privacy and security. Your private keys are never uploaded to the server. There is no address re-use, since each invoice generates a new address deriving from your xpubkey.
BTCPay is a non-custodial invoicing system which eliminates the involvement of a third-party. Payments with BTCPay go directly to your wallet, which increases the privacy and security. Your private keys are never uploaded to the server. There is no address re-use since each invoice generates a new address deriving from your xpubkey.
The software is built in C# and conforms to the invoice [API of BitPay](https://bitpay.com/api). It allows for your website to be easily migrated from BitPay and configured as a self-hosted payment processor.
@ -26,8 +26,8 @@ Thanks to the apps built on top of it, you can use BTCPay to receive donations o
* Lightning Network support (LND and c-lightning)
* Altcoin support
* Complete control over private keys
* Fully compatability with BitPay API (easy migration)
* Enchanced privacy
* Full compatibility with BitPay API (easy migration)
* Enhanced privacy
* SegWit support
* Process payments for others
* Payment buttons
@ -60,9 +60,9 @@ You can also read the [BTCPay Merchants Guide](https://www.reddit.com/r/Bitcoin/
## How to build
While the documentation advise using docker-compose, you may want to build yourself outside of development purpose.
While the documentation advises to use docker-compose, you may want to build BTCPay yourself.
First install .NET Core SDK 2.1 as specified by [Microsoft website](https://www.microsoft.com/net/download/dotnet-core/2.1).
First install .NET Core SDK v2.1.4 (with patch version >= 402) as specified by [Microsoft website](https://www.microsoft.com/net/download/dotnet-core/2.1).
On Powershell:
```
@ -76,7 +76,7 @@ On linux:
## How to run
Use the `run` scripts to run BTCPayServer, this example show how to print the available command line arguments of BTCPayServer.
Use the `run` scripts to run BTCPayServer, this example shows how to print the available command line arguments of BTCPayServer.
On Powershell:
```
@ -90,4 +90,4 @@ On linux:
## Other dependencies
For more information see the documentation [How to deploy a BTCPay server instance](https://github.com/btcpayserver/btcpayserver-doc/#deployment).
For more information, see the documentation: [How to deploy a BTCPay server instance](https://github.com/btcpayserver/btcpayserver-doc/#deployment).

5
global.json Normal file
View File

@ -0,0 +1,5 @@
{
"sdk": {
"version": "2.1.402"
}
}