Compare commits

..

13 Commits

Author SHA1 Message Date
d53c987f2e bump 2018-12-06 17:25:50 +09:00
682693a9f0 Update translations 2018-12-06 17:23:42 +09:00
e836faf792 Stop setting BIP70 link info 2018-12-06 17:12:51 +09:00
6e27233be8 Remove BIP70 support 2018-12-06 17:08:28 +09:00
9209984a2f Remove useless argument from GetInvoice 2018-12-06 17:05:27 +09:00
1477630c78 Remove anonymous access to invoice data 2018-12-06 16:58:04 +09:00
ab670080c7 bump 2018-12-06 12:29:13 +09:00
8198f98376 Code simplification 2018-12-06 12:26:42 +09:00
65b4697229 Properly error 401 if request is not signed correctly 2018-12-06 12:22:05 +09:00
e75a1a8b70 Improve ledger feedback for asking authorization to access xpub 2018-12-04 21:22:27 +09:00
5a958da84d bump 2018-12-04 13:04:56 +09:00
cad602ad14 Fix several issues in cart
* Fix: Only USD currency with 2 decimals were properly handled for tips
* Fix: All PoS apps would were sharing the same basket
* Fix: Currency formatting was not using server side information
* Fix: Various bug of formatting for decimal 0 and more than 2.
2018-12-04 13:04:26 +09:00
1f14bd6188 Add button and qr code to the bitpay translator 2018-12-04 11:53:25 +09:00
24 changed files with 153 additions and 410 deletions

View File

@ -350,57 +350,6 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Trait("Integration", "Integration")]
public void CanPayUsingBIP70()
{
using (var tester = ServerTester.Create())
{
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
var invoice = user.BitPay.CreateInvoice(new Invoice()
{
Buyer = new Buyer() { email = "test@fwf.com" },
Price = 5000.0m,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
//RedirectURL = redirect + "redirect",
//NotificationURL = CallbackUri + "/notification",
ItemDesc = "Some description",
FullNotifications = true
}, Facade.Merchant);
Assert.False(invoice.Refundable);
var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
var request = url.GetPaymentRequest();
var payment = request.CreatePayment();
Transaction tx = new Transaction();
tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
var cashCow = tester.ExplorerNode;
tx = cashCow.FundRawTransaction(tx).Transaction;
tx = cashCow.SignRawTransaction(tx);
payment.Transactions.Add(tx);
payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
var ack = payment.SubmitPayment();
Assert.NotNull(ack);
Eventually(() =>
{
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("paid", localInvoice.Status);
Assert.True(localInvoice.Refundable);
});
}
}
[Fact]
[Trait("Integration", "Integration")]
public async Task CanSetLightningServer()
@ -1801,7 +1750,7 @@ ReceivedDate,StoreId,OrderId,InvoiceId,CreatedDate,ExpirationDate,MonitoringDate
Assert.True(IsMapped(invoice, ctx));
cashCow.SendToAddress(invoiceAddress, firstPayment);
var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
var invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
Assert.Single(invoiceEntity.HistoricalAddresses);
Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned);
@ -1819,7 +1768,7 @@ ReceivedDate,StoreId,OrderId,InvoiceId,CreatedDate,ExpirationDate,MonitoringDate
Assert.True(IsMapped(invoice, ctx));
Assert.True(IsMapped(localInvoice, ctx));
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult();
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress);
Assert.NotNull(historical1.UnAssigned);
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress);

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<Version>1.0.3.22</Version>
<Version>1.0.3.25</Version>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
</PropertyGroup>
<PropertyGroup>

View File

@ -41,6 +41,7 @@ namespace BTCPayServer.Controllers
var currency = _AppsHelper.GetCurrencyData(settings.Currency, false);
double step = currency == null ? 1 : Math.Pow(10, -(currency.Divisibility));
var numberFormatInfo = _AppsHelper.Currencies.GetNumberFormatInfo(currency.Code) ?? _AppsHelper.Currencies.GetNumberFormatInfo("USD");
return View(new ViewPointOfSaleViewModel()
{
Title = settings.Title,
@ -49,6 +50,14 @@ namespace BTCPayServer.Controllers
ShowCustomAmount = settings.ShowCustomAmount,
CurrencyCode = currency.Code,
CurrencySymbol = currency.Symbol,
CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData()
{
CurrencySymbol = string.IsNullOrEmpty(currency.Symbol) ? currency.Code : currency.Symbol,
Divisibility = currency.Divisibility,
DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator,
ThousandSeparator = numberFormatInfo.NumberGroupSeparator,
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern)
},
Items = _AppsHelper.Parse(settings.Template, settings.Currency),
ButtonText = settings.ButtonText,
CustomButtonText = settings.CustomButtonText,
@ -124,7 +133,7 @@ namespace BTCPayServer.Controllers
{
ApplicationDbContextFactory _ContextFactory;
CurrencyNameTable _Currencies;
public CurrencyNameTable Currencies => _Currencies;
public AppsHelper(ApplicationDbContextFactory contextFactory, CurrencyNameTable currencies)
{
_ContextFactory = contextFactory;

View File

@ -10,6 +10,7 @@ using NBitcoin.Payment;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Controllers
{
@ -49,7 +50,7 @@ namespace BTCPayServer.Controllers
try
{
BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink);
if(!urlBuilder.PaymentRequestUrl.DnsSafeHost.EndsWith("bitpay.com", StringComparison.OrdinalIgnoreCase))
if (!urlBuilder.PaymentRequestUrl.DnsSafeHost.EndsWith("bitpay.com", StringComparison.OrdinalIgnoreCase))
{
throw new Exception("This tool only work with bitpay");
}
@ -60,9 +61,19 @@ namespace BTCPayServer.Controllers
var result = await client.SendAsync(request);
// {"network":"main","currency":"BTC","requiredFeeRate":29.834,"outputs":[{"amount":255900,"address":"1PgPo5d4swD6pKfCgoXtoW61zqTfX9H7tj"}],"time":"2018-12-03T14:39:47.162Z","expires":"2018-12-03T14:54:47.162Z","memo":"Payment request for BitPay invoice HHfG8cprRMzZG6MErCqbjv for merchant VULTR Holdings LLC","paymentUrl":"https://bitpay.com/i/HHfG8cprRMzZG6MErCqbjv","paymentId":"HHfG8cprRMzZG6MErCqbjv"}
var str = await result.Content.ReadAsStringAsync();
var jobj = JObject.Parse(str);
vm.Address = ((JArray)jobj["outputs"])[0]["address"].Value<string>();
vm.Amount = Money.Satoshis(((JArray)jobj["outputs"])[0]["amount"].Value<long>()).ToString();
try
{
var jobj = JObject.Parse(str);
vm.Address = ((JArray)jobj["outputs"])[0]["address"].Value<string>();
var amount = Money.Satoshis(((JArray)jobj["outputs"])[0]["amount"].Value<long>());
vm.Amount = amount.ToString();
vm.BitcoinUri = $"bitcoin:{vm.Address}?amount={amount.ToString()}";
}
catch (JsonReaderException)
{
ModelState.AddModelError(nameof(vm.BitpayLink), $"Invalid or expired bitpay invoice");
return View(vm);
}
}
catch (Exception ex)
{

View File

@ -40,16 +40,18 @@ namespace BTCPayServer.Controllers
[HttpGet]
[Route("invoices/{id}")]
[AllowAnonymous]
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id, string token)
public async Task<DataWrapper<InvoiceResponse>> GetInvoice(string id)
{
var invoice = await _InvoiceRepository.GetInvoice(null, id);
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
{
InvoiceId = id,
StoreId = new[] { HttpContext.GetStoreData().Id }
})).FirstOrDefault();
if (invoice == null)
throw new BitpayHttpException(404, "Object not found");
var resp = invoice.EntityToDTO(_NetworkProvider);
return new DataWrapper<InvoiceResponse>(resp);
}
[HttpGet]
[Route("invoices")]
public async Task<DataWrapper<InvoiceResponse[]>> GetInvoices(

View File

@ -1,117 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Filters;
using BTCPayServer.Logging;
using BTCPayServer.Payments;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.Payment;
namespace BTCPayServer.Controllers
{
public partial class InvoiceController
{
[HttpGet]
[Route("i/{invoiceId}/{cryptoCode?}")]
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
public async Task<IActionResult> GetInvoiceRequest(string invoiceId, string cryptoCode = null)
{
if (cryptoCode == null)
cryptoCode = "BTC";
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
var network = _NetworkProvider.GetNetwork(cryptoCode);
var paymentMethodId = new PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike);
if (invoice == null || invoice.IsExpired() || network == null || !invoice.Support(paymentMethodId))
return NotFound();
var dto = invoice.EntityToDTO(_NetworkProvider);
var paymentMethod = dto.CryptoInfo.First(c => c.GetpaymentMethodId() == paymentMethodId);
PaymentRequest request = new PaymentRequest
{
DetailsVersion = 1
};
request.Details.Expires = invoice.ExpirationTime;
request.Details.Memo = invoice.ProductInformation.ItemDesc;
request.Details.Network = network.NBitcoinNetwork;
request.Details.Outputs.Add(new PaymentOutput() { Amount = paymentMethod.Due, Script = BitcoinAddress.Create(paymentMethod.Address, network.NBitcoinNetwork).ScriptPubKey });
request.Details.MerchantData = Encoding.UTF8.GetBytes(invoice.Id);
request.Details.Time = DateTimeOffset.UtcNow;
request.Details.PaymentUrl = new Uri(invoice.ServerUrl.WithTrailingSlash() + ($"i/{invoice.Id}"), UriKind.Absolute);
var store = await _StoreRepository.FindStore(invoice.StoreId);
if (store == null)
throw new BitpayHttpException(401, "Unknown store");
if (store.StoreCertificate != null)
{
try
{
request.Sign(store.StoreCertificate, PKIType.X509SHA256);
}
catch (Exception ex)
{
Logs.PayServer.LogWarning(ex, "Error while signing payment request");
}
}
return new PaymentRequestActionResult(request);
}
[HttpPost]
[Route("i/{invoiceId}", Order = 99)]
[Route("i/{invoiceId}/{cryptoCode}", Order = 99)]
[MediaTypeConstraint("application/bitcoin-payment")]
public async Task<IActionResult> PostPayment(string invoiceId, string cryptoCode = null)
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
if (cryptoCode == null)
cryptoCode = "BTC";
var network = _NetworkProvider.GetNetwork(cryptoCode);
if (network == null || invoice == null || invoice.IsExpired() || !invoice.Support(new PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike)))
return NotFound();
var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
return NotFound();
var payment = PaymentMessage.Load(Request.Body, network.NBitcoinNetwork);
var unused = wallet.BroadcastTransactionsAsync(payment.Transactions);
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray(), network.NBitcoinNetwork);
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
}
}
public class PaymentRequestActionResult : IActionResult
{
PaymentRequest req;
public PaymentRequestActionResult(PaymentRequest req)
{
this.req = req;
}
public Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest";
req.WriteTo(context.HttpContext.Response.Body);
return Task.CompletedTask;
}
}
public class PaymentAckActionResult : IActionResult
{
PaymentACK req;
public PaymentAckActionResult(PaymentACK req)
{
this.req = req;
}
public Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary";
context.HttpContext.Response.ContentType = "application/bitcoin-paymentack";
req.WriteTo(context.HttpContext.Response.Body);
return Task.CompletedTask;
}
}
}

View File

@ -30,11 +30,13 @@ namespace BTCPayServer.Controllers
{
[HttpGet]
[Route("invoices/{invoiceId}")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
public async Task<IActionResult> Invoice(string invoiceId)
{
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
{
InvoiceId = invoiceId,
UserId = GetUserId(),
IncludeAddresses = true,
IncludeEvents = true
})).FirstOrDefault();
@ -209,7 +211,7 @@ namespace BTCPayServer.Controllers
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, string paymentMethodIdStr)
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
if (invoice == null)
return null;
var store = await _StoreRepository.FindStore(invoice.StoreId);
@ -369,7 +371,7 @@ namespace BTCPayServer.Controllers
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
return NotFound();
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
if (invoice == null || invoice.Status == "complete" || invoice.Status == "invalid" || invoice.Status == "expired")
return NotFound();
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
@ -591,7 +593,11 @@ namespace BTCPayServer.Controllers
[BitpayAPIConstraint(false)]
public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId)
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
{
InvoiceId = invoiceId,
UserId = GetUserId()
})).FirstOrDefault();
if (invoice == null)
return NotFound();
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);

View File

@ -330,7 +330,7 @@ namespace BTCPayServer.HostedServices
{
leases.Add(_EventAggregator.Subscribe<InvoiceEvent>(async e =>
{
var invoice = await _InvoiceRepository.GetInvoice(null, e.Invoice.Id);
var invoice = await _InvoiceRepository.GetInvoice(e.Invoice.Id);
if (invoice == null)
return;
List<Task> tasks = new List<Task>();

View File

@ -208,7 +208,7 @@ namespace BTCPayServer.HostedServices
private async Task Wait(string invoiceId)
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
try
{
var delay = invoice.ExpirationTime - DateTimeOffset.UtcNow;
@ -283,7 +283,7 @@ namespace BTCPayServer.HostedServices
loopCount++;
try
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true);
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true);
if (invoice == null)
break;
var updateContext = new UpdateInvoiceContext(invoice);

View File

@ -22,6 +22,17 @@ namespace BTCPayServer.Models.AppViewModels
public bool Custom { get; set; }
}
public class CurrencyInfoData
{
public bool Prefixed { get; set; }
public string CurrencySymbol { get; set; }
public string ThousandSeparator { get; set; }
public string DecimalSeparator { get; set; }
public int Divisibility { get; internal set; }
}
public CurrencyInfoData CurrencyInfo { get; set; }
public bool EnableShoppingCart { get; set; }
public bool ShowCustomAmount { get; set; }
public string Step { get; set; }
@ -29,7 +40,7 @@ namespace BTCPayServer.Models.AppViewModels
public Item[] Items { get; set; }
public string CurrencyCode { get; set; }
public string CurrencySymbol { get; set; }
public string AppId { get; set; }
public string ButtonText { get; set; }
public string CustomButtonText { get; set; }
public string CustomTipText { get; set; }

View File

@ -12,5 +12,6 @@ namespace BTCPayServer.Models
public string BitpayLink { get; set; }
public string Address { get; set; }
public string Amount { get; set; }
public string BitcoinUri { get; set; }
}
}

View File

@ -205,7 +205,7 @@ namespace BTCPayServer.Payments.Bitcoin
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, string invoiceId)
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, false);
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, false);
if (invoice == null)
return null;
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
@ -315,7 +315,7 @@ namespace BTCPayServer.Payments.Bitcoin
var invoices = await _InvoiceRepository.GetPendingInvoices();
foreach (var invoiceId in invoices)
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true);
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true);
if (invoice == null)
continue;
var alreadyAccounted = GetAllBitcoinPaymentData(invoice).Select(p => p.Outpoint).ToHashSet();

View File

@ -73,7 +73,7 @@ namespace BTCPayServer.Payments.Lightning
{
if (Listening(invoiceId))
return;
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
foreach (var paymentMethod in invoice.GetPaymentMethods(_NetworkProvider)
.Where(c => c.GetId().PaymentType == PaymentTypes.LightningLike))
{
@ -194,7 +194,7 @@ namespace BTCPayServer.Payments.Lightning
}, network.CryptoCode, accounted: true);
if (payment != null)
{
var invoice = await _InvoiceRepository.GetInvoice(null, listenedInvoice.InvoiceId);
var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId);
if (invoice != null)
_Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment"));
}

View File

@ -5,7 +5,7 @@
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_NETWORK": "regtest",
"BTCPAY_BUNDLEJSCSS": "true",
"BTCPAY_BUNDLEJSCSS": "false",
"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",
@ -24,7 +24,7 @@
"BTCPAY_NETWORK": "regtest",
"BTCPAY_PORT": "14142",
"BTCPAY_HttpsUseDefaultCertificate": "true",
"BTCPAY_BUNDLEJSCSS": "true",
"BTCPAY_BUNDLEJSCSS": "false",
"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",

View File

@ -65,18 +65,14 @@ namespace BTCPayServer.Security
{
var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims);
storeId = result.StoreId;
failedAuth = !result.SuccessAuth;
successAuth = result.SuccessAuth;
failedAuth = !successAuth;
}
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
{
storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization);
if (storeId == null)
{
Logs.PayServer.LogDebug("API key check failed");
failedAuth = true;
}
successAuth = storeId != null;
failedAuth = !successAuth;
}
if (failedAuth)
@ -148,6 +144,10 @@ namespace BTCPayServer.Security
storeId = bitToken.StoreId;
}
}
else
{
return (storeId, false);
}
}
catch (FormatException) { }
return (storeId, true);

View File

@ -395,9 +395,6 @@ namespace BTCPayServer.Services.Invoices
var cryptoSuffix = cryptoInfo.CryptoCode == "BTC" ? "" : "/" + cryptoInfo.CryptoCode;
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
{
BIP72 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}&r={ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}")}",
BIP72b = $"{scheme}:?r={ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}")}",
BIP73 = ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}"),
BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}",
};
}

View File

@ -340,7 +340,7 @@ namespace BTCPayServer.Services.Invoices
await context.SaveChangesAsync().ConfigureAwait(false);
}
}
public async Task<InvoiceEntity> GetInvoice(string storeId, string id, bool inludeAddressData = false)
public async Task<InvoiceEntity> GetInvoice(string id, bool inludeAddressData = false)
{
using (var context = _ContextFactory.CreateContext())
{
@ -353,9 +353,6 @@ namespace BTCPayServer.Services.Invoices
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
query = query.Where(i => i.Id == id);
if (storeId != null)
query = query.Where(i => i.StoreDataId == storeId);
var invoice = await query.FirstOrDefaultAsync().ConfigureAwait(false);
if (invoice == null)
return null;

View File

@ -61,7 +61,15 @@ namespace BTCPayServer.Services.Rates
currencyInfo.CurrencySymbol = currency;
return currencyInfo;
}
public NumberFormatInfo GetNumberFormatInfo(string currency)
{
var curr = GetCurrencyProvider(currency);
if (curr is CultureInfo cu)
return cu.NumberFormat;
if (curr is NumberFormatInfo ni)
return ni;
return null;
}
public IFormatProvider GetCurrencyProvider(string currency)
{
lock (_CurrencyProviders)

View File

@ -9,6 +9,13 @@
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<p>You need to pay <b>@Model.Amount</b> to <b>@Model.Address</b></p>
<div id="qrCode"></div>
<div id="qrCodeData" data-url="@Html.Raw(Model.BitcoinUri)" style="margin-bottom:20px;"></div>
<p>
<a class="btn btn-primary" href="@Model.BitcoinUri">
<span>Open in wallet</span>
</a>
</p>
</div>
</div>
</div>
@ -36,5 +43,19 @@
</div>
<div class="col-lg-4 text-center">&nbsp;</div>
</div>
</div>
</div>
</section>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<script type="text/javascript" src="~/js/qrcode.min.js"></script>
<script type="text/javascript">
new QRCode(document.getElementById("qrCode"),
{
text: "@Html.Raw(Model.BitcoinUri)",
width: 150,
height: 150
});
$("#qrCode > img").css({ "margin": "auto" });
</script>
}

View File

@ -15,7 +15,7 @@
<div class="row">
<div class="col-md-8">
<form method="post">
@if(!Model.Confirmation)
@if (!Model.Confirmation)
{
<div class="form-group">
<h5>Derivation Scheme</h5>
@ -26,13 +26,19 @@
<label asp-for="DerivationScheme"></label>
<input asp-for="DerivationScheme" class="form-control" />
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
<div id="ledger-loading" class="form-text text-muted">
Checking if a ledger wallet is connected...
</div>
<div id="ledger-validate" class="form-text text-muted" style="display: none;">
Please validate access on your screen...
</div>
<p id="no-ledger-info" class="form-text text-muted" style="display: none;">
No ledger wallet detected. If you own one, use chrome, open the app, and refresh this page.
</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?</span>
<ul>
@for(int i = 0; i < 4; i++)
@for (int i = 0; i < 4; i++)
{
<li><a class="ledger-info-recommended" data-ledgeraccount="@i" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a></li>
}
@ -100,7 +106,7 @@
</tr>
</thead>
<tbody>
@foreach(var sample in Model.AddressSamples)
@foreach (var sample in Model.AddressSamples)
{
<tr>
<td>@sample.KeyPath</td>

View File

@ -245,215 +245,48 @@ Cart.prototype.formatCurrency = function(amount, currency, symbol) {
thousandsSep = '',
decimalSep = ''
prefix = '',
postfix = '',
currencyName = currency.toLowerCase();
postfix = '';
// Currency symbols
switch (currencyName) {
case 'usd':
case 'aud':
case 'cad':
case 'clp':
case 'mxn':
case 'nzd':
case 'sgd':
prefix = '$';
break;
case 'eur':
postfix = ' €';
break;
case 'chf':
prefix = 'CHF ';
break;
case 'gbp':
prefix = '£';
break;
case 'jpy':
case 'cny':
prefix = '¥';
break;
case 'hkd':
prefix = 'HK$';
break;
case 'ars':
case 'cop':
prefix = '$ ';
break;
case 'brl':
prefix = 'R$ ';
break;
case 'bdt':
postfix = '৳';
break;
case 'czk':
postfix = ' Kč';
break;
case 'dkk':
postfix = ' kr.';
break;
case 'huf':
postfix = ' Ft';
break;
case 'hrk':
postfix = ' HRK';
break;
case 'ils':
postfix = ' ₪';
break;
case 'inr':
prefix = '₹ ';
break;
case 'isk':
postfix = ' ISK';
break;
case 'kzt':
postfix = ' ₸';
break;
case 'myr':
prefix = 'RM';
break;
case 'ngn':
prefix = '₦';
break;
case 'nok':
prefix = 'kr ';
break;
case 'php':
prefix = '₱';
break;
case 'pen':
prefix = 'S/';
break;
case 'pln':
postfix = ' zł';
break;
case 'ron':
postfix = ' RON';
break;
case 'rub':
postifx = ' ₽';
break;
case 'sek':
postfix = ' kr';
break;
case 'aed':
prefix = 'د.إ. ';
break;
case 'egp':
prefix = 'ج.م.';
break;
case 'irr':
prefix = 'ریال';
break;
case 'pkr':
prefix = ' ر';
break;
case 'sar':
prefix = 'ر.س.';
break;
case 'try':
prefix = '₺';
break;
case 'uah':
postfix = ' ₴';
break;
case 'vnd':
postfix = ' ₫';
break;
case 'zar':
prefix = 'R';
break;
default:
prefix = symbol || currency;
if (srvModel.currencyInfo.prefixed) {
prefix = srvModel.currencyInfo.currencySymbol;
}
// Currency separators
switch (currencyName) {
case 'eur':
case 'czk':
case 'huf':
case 'kzt':
case 'nok':
case 'pln':
case 'rub':
case 'sek':
case 'uah':
case 'zar':
thousandsSep = ' ';
decimalSep = ',';
break;
case 'chf':
thousandsSep = '';
decimalSep = '.';
break;
case 'cop':
case 'clp':
case 'idr':
case 'isk':
case 'vnd':
thousandsSep = '.';
break;
case 'jpy':
thousandsSep = ',';
break;
case 'ars':
case 'brl':
case 'dkk':
case 'hrk':
case 'ron':
case 'try':
thousandsSep = '.';
decimalSep = ',';
break;
case 'irr':
case 'pkr':
thousandsSep = '٬';
break;
case 'egp':
case 'sar':
thousandsSep = '٬';
decimalSep = '٫';
break;
default:
thousandsSep = ',';
decimalSep = '.';
else {
postfix = srvModel.currencyInfo.currencySymbol;
}
thousandsSep = srvModel.currencyInfo.thousandSeparator;
decimalSep = srvModel.currencyInfo.decimalSeparator;
amt = amount.toFixed(srvModel.currencyInfo.divisibility);
if (decimalSep !== '') {
// Replace decimal separator
amt = amount.toFixed(2).replace(/.(\d{2})$/g, decimalSep + '\$1');
} else {
// No decimal separator
amt = amount.toString();
}
// Add currency sign and thousands separator
amt = prefix + amt.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep) + postfix;
var splittedAmount = amt.split('.');
amt = (splittedAmount[0] + '.').replace(/(\d)(?=(\d{3})+\.)/g, '$1' + thousandsSep);
amt = amt.substr(0, amt.length - 1);
if(splittedAmount.length == 2)
amt = amt + '.' + splittedAmount[1];
if (srvModel.currencyInfo.divisibility !== 0) {
amt[amt.length - srvModel.currencyInfo.divisibility - 1] = decimalSep;
}
amt = prefix + amt + postfix;
return amt;
}
Cart.prototype.toCents = function(num) {
return num * 100;
return num * Math.pow(10, srvModel.currencyInfo.divisibility);
}
Cart.prototype.fromCents = function(num) {
return num / 100;
return num / Math.pow(10, srvModel.currencyInfo.divisibility);
}
Cart.prototype.getStorageKey = function () { return ('cart' + srvModel.appId + srvModel.currencyCode); }
Cart.prototype.saveLocalStorage = function() {
localStorage.setItem('cart', JSON.stringify(this.content));
localStorage.setItem(this.getStorageKey(), JSON.stringify(this.content));
}
Cart.prototype.loadLocalStorage = function() {
this.content = $.parseJSON(localStorage.getItem('cart')) || [];
this.content = $.parseJSON(localStorage.getItem(this.getStorageKey())) || [];
// Get number of cart items
for (var key in this.content) {
@ -464,9 +297,9 @@ Cart.prototype.loadLocalStorage = function() {
}
Cart.prototype.destroy = function() {
localStorage.removeItem('cart');
localStorage.removeItem(this.getStorageKey());
this.content = [];
this.items = 0;
this.totalAmount = 0;
this.tip = 0;
}
}

View File

@ -20,22 +20,32 @@
function WriteAlert(type, message) {
}
function showFeedback(id) {
$("#ledger-loading").css("display", id === "ledger-loading" ? "block" : "none");
$("#no-ledger-info").css("display", id === "no-ledger-info" ? "block" : "none");
$("#ledger-validate").css("display", id === "ledger-validate" ? "block" : "none");
$("#ledger-info").css("display", id === "ledger-info" ? "block" : "none");
}
function Write(prefix, type, message) {
if (type === "error") {
$("#no-ledger-info").css("display", "block");
$("#ledger-in fo").css("display", "none");
showFeedback("no-ledger-info");
}
}
$(".ledger-info-recommended").on("click", function (elem) {
elem.preventDefault();
showFeedback("ledger-validate");
var account = elem.currentTarget.getAttribute("data-ledgeraccount");
var cryptoCode = GetSelectedCryptoCode();
bridge.sendCommand("getxpub", "cryptoCode=" + cryptoCode + "&account=" + account)
.then(function (result) {
if (cryptoCode !== GetSelectedCryptoCode())
return;
showFeedback("ledger-info");
$("#DerivationScheme").val(result.extPubKey);
$("#DerivationSchemeFormat").val("BTCPay");
})
@ -60,8 +70,7 @@
}
else {
Write('check', 'success', 'This store is configured to use your ledger');
$("#no-ledger-info").css("display", "none");
$("#ledger-info").css("display", "block");
showFeedback("ledger-info");
}
});
};

View File

@ -33,7 +33,7 @@
"What happened?": "¿Qué sucedió?",
"InvoiceExpired_Body_1": "Esta factura ha expirado. Una factura solo es válida por {{maxTimeMinutes}} minutos. \nPuedes regresar a {{storeName}} si deseas volver a enviar tu pago.",
"InvoiceExpired_Body_2": "Si intentaste enviar un pago, aún no ha sido aceptado por la red de Bitcoin. Todavía no hemos recibido tus fondos.",
"InvoiceExpired_Body_3": "",
"InvoiceExpired_Body_3": "Si recibimos el pago despues, procesaremos tu orden o te contactaremos para un reembolso...",
"Invoice ID": "ID de la factura",
"Order ID": "ID del pedido",
"Return to StoreName": "Regresar a {{storeName}}",

View File

@ -33,7 +33,7 @@
"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_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": "",
"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",
"Order ID": "Broj narudžbine",
"Return to StoreName": "Vrati se na {{storeName}}",