Compare commits

..

7 Commits

14 changed files with 212 additions and 20 deletions

@ -1298,6 +1298,25 @@ namespace BTCPayServer.Tests
Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled);
Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC"));
Assert.True(invoice.PaymentTotals.ContainsKey("LTC"));
// Check if we can disable LTC
invoice = user.BitPay.CreateInvoice(new Invoice()
{
Price = 5000.0m,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true,
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
{
{ "BTC", new InvoiceSupportedTransactionCurrency() { Enabled = true } }
}
}, Facade.Merchant);
Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC"));
Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC"));
}
}

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

@ -172,7 +172,7 @@ namespace BTCPayServer.Controllers
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
try
{
var invoice = await _InvoiceController.CreateInvoiceCore(new Invoice()
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
{
Currency = settings.TargetCurrency,
ItemCode = request.ChoiceKey ?? string.Empty,
@ -250,7 +250,7 @@ namespace BTCPayServer.Controllers
}
var store = await _AppService.GetStore(app);
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice()
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
{
ItemCode = choice?.Id,
ItemDesc = title,

@ -32,8 +32,10 @@ namespace BTCPayServer.Controllers
[HttpPost]
[Route("invoices")]
[MediaTypeConstraint("application/json")]
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] Invoice invoice)
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] CreateInvoiceRequest invoice)
{
if (invoice == null)
throw new BitpayHttpException(400, "Invalid invoice");
return await _InvoiceController.CreateInvoiceCore(invoice, HttpContext.GetStoreData(), HttpContext.Request.GetAbsoluteRoot());
}

@ -578,7 +578,7 @@ namespace BTCPayServer.Controllers
try
{
var result = await CreateInvoiceCore(new Invoice()
var result = await CreateInvoiceCore(new CreateInvoiceRequest()
{
Price = model.Amount.Value,
Currency = model.Currency,

@ -62,7 +62,7 @@ namespace BTCPayServer.Controllers
}
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, List<string> additionalTags = null)
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string> additionalTags = null)
{
if (!store.HasClaim(Policies.CanCreateInvoice.Key))
throw new UnauthorizedAccessException();
@ -89,7 +89,7 @@ namespace BTCPayServer.Controllers
entity.NotificationURL = notificationUri.AbsoluteUri;
}
entity.NotificationEmail = invoice.NotificationEmail;
entity.BuyerInformation = Map<Invoice, BuyerInformation>(invoice);
entity.BuyerInformation = Map<CreateInvoiceRequest, BuyerInformation>(invoice);
entity.PaymentTolerance = storeBlob.PaymentTolerance;
if (additionalTags != null)
entity.InternalTags.AddRange(additionalTags);
@ -112,7 +112,7 @@ namespace BTCPayServer.Controllers
invoice.TaxIncluded = Math.Max(0.0m, invoice.TaxIncluded);
invoice.TaxIncluded = Math.Min(invoice.TaxIncluded, invoice.Price);
entity.ProductInformation = Map<Invoice, ProductInformation>(invoice);
entity.ProductInformation = Map<CreateInvoiceRequest, ProductInformation>(invoice);
entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite;
@ -125,6 +125,17 @@ namespace BTCPayServer.Controllers
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
var rules = storeBlob.GetRateRules(_NetworkProvider);
var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any()
if (invoice.SupportedTransactionCurrencies != null && invoice.SupportedTransactionCurrencies.Count != 0)
{
var supportedTransactionCurrencies = invoice.SupportedTransactionCurrencies
.Where(c => c.Value.Enabled)
.Select(c => PaymentMethodId.TryParse(c.Key, out var p) ? p : null)
.ToHashSet();
excludeFilter = PaymentFilter.Or(excludeFilter,
PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p)));
}
foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider)
.Where(s => !excludeFilter.Match(s.PaymentId))
.Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode))

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Filters;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Cors;
@ -45,7 +46,7 @@ namespace BTCPayServer.Controllers
if (!ModelState.IsValid)
return View();
var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice()
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
{
Price = model.Price,
Currency = model.Currency,

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBitcoin.JsonConverters;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Models
{
public class CreateInvoiceRequest
{
[JsonProperty(PropertyName = "buyer")]
public Buyer Buyer { get; set; }
[JsonProperty(PropertyName = "buyerEmail", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerEmail { get; set; }
[JsonProperty(PropertyName = "buyerCountry", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerCountry { get; set; }
[JsonProperty(PropertyName = "buyerZip", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerZip { get; set; }
[JsonProperty(PropertyName = "buyerState", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerState { get; set; }
[JsonProperty(PropertyName = "buyerCity", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerCity { get; set; }
[JsonProperty(PropertyName = "buyerAddress2", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerAddress2 { get; set; }
[JsonProperty(PropertyName = "buyerAddress1", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerAddress1 { get; set; }
[JsonProperty(PropertyName = "buyerName", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerName { get; set; }
[JsonProperty(PropertyName = "physical", DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool Physical { get; set; }
[JsonProperty(PropertyName = "redirectURL", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string RedirectURL { get; set; }
[JsonProperty(PropertyName = "notificationURL", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string NotificationURL { get; set; }
[JsonProperty(PropertyName = "extendedNotifications", DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool ExtendedNotifications { get; set; }
[JsonProperty(PropertyName = "fullNotifications", DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool FullNotifications { get; set; }
[JsonProperty(PropertyName = "transactionSpeed", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string TransactionSpeed { get; set; }
[JsonProperty(PropertyName = "buyerPhone", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string BuyerPhone { get; set; }
[JsonProperty(PropertyName = "posData", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string PosData { get; set; }
[JsonProperty(PropertyName = "itemCode", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string ItemCode { get; set; }
[JsonProperty(PropertyName = "itemDesc", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string ItemDesc { get; set; }
[JsonProperty(PropertyName = "orderId", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string OrderId { get; set; }
[JsonProperty(PropertyName = "currency", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Currency { get; set; }
[JsonProperty(PropertyName = "price", DefaultValueHandling = DefaultValueHandling.Ignore)]
public decimal Price { get; set; }
[JsonProperty(PropertyName = "notificationEmail", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string NotificationEmail { get; set; }
[JsonConverter(typeof(DateTimeJsonConverter))]
[JsonProperty(PropertyName = "expirationTime", DefaultValueHandling = DefaultValueHandling.Ignore)]
public DateTimeOffset? ExpirationTime { get; set; }
[JsonProperty(PropertyName = "status", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Status { get; set; }
[JsonProperty(PropertyName = "minerFees", DefaultValueHandling = DefaultValueHandling.Ignore)]
public Dictionary<string, MinerFeeInfo> MinerFees { get; set; }
[JsonProperty(PropertyName = "supportedTransactionCurrencies", DefaultValueHandling = DefaultValueHandling.Ignore)]
public Dictionary<string, InvoiceSupportedTransactionCurrency> SupportedTransactionCurrencies { get; set; }
[JsonProperty(PropertyName = "exchangeRates", DefaultValueHandling = DefaultValueHandling.Ignore)]
public Dictionary<string, Dictionary<string, decimal>> ExchangeRates { get; set; }
[JsonProperty(PropertyName = "refundable", DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool Refundable { get; set; }
[JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)]
public decimal TaxIncluded { get; set; }
[JsonProperty(PropertyName = "nonce", DefaultValueHandling = DefaultValueHandling.Ignore)]
public long Nonce { get; set; }
[JsonProperty(PropertyName = "guid", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Guid { get; set; }
[JsonProperty(PropertyName = "token", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Token { get; set; }
}
}

@ -12,6 +12,21 @@ namespace BTCPayServer.Payments
public class PaymentFilter
{
class OrPaymentFilter : IPaymentFilter
{
private readonly IPaymentFilter _a;
private readonly IPaymentFilter _b;
public OrPaymentFilter(IPaymentFilter a, IPaymentFilter b)
{
_a = a;
_b = b;
}
public bool Match(PaymentMethodId paymentMethodId)
{
return _a.Match(paymentMethodId) || _b.Match(paymentMethodId);
}
}
class NeverPaymentFilter : IPaymentFilter
{
@ -54,6 +69,34 @@ namespace BTCPayServer.Payments
return paymentMethodId == _paymentMethodId;
}
}
class PredicateFilter : IPaymentFilter
{
private Func<PaymentMethodId, bool> predicate;
public PredicateFilter(Func<PaymentMethodId, bool> predicate)
{
this.predicate = predicate;
}
public bool Match(PaymentMethodId paymentMethodId)
{
return this.predicate(paymentMethodId);
}
}
public static IPaymentFilter Where(Func<PaymentMethodId, bool> predicate)
{
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
return new PredicateFilter(predicate);
}
public static IPaymentFilter Or(IPaymentFilter a, IPaymentFilter b)
{
if (a == null)
throw new ArgumentNullException(nameof(a));
if (b == null)
throw new ArgumentNullException(nameof(b));
return new OrPaymentFilter(a, b);
}
public static IPaymentFilter Never()
{
return NeverPaymentFilter.Instance;

@ -67,10 +67,37 @@ namespace BTCPayServer.Payments
return CryptoCode + "_" + PaymentType.ToString();
}
public static bool TryParse(string str, out PaymentMethodId paymentMethodId)
{
paymentMethodId = null;
var parts = str.Split('_', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0 || parts.Length > 2)
return false;
PaymentTypes type = PaymentTypes.BTCLike;
if (parts.Length == 2)
{
switch (parts[1].ToLowerInvariant())
{
case "btclike":
case "onchain":
type = PaymentTypes.BTCLike;
break;
case "lightninglike":
case "offchain":
type = PaymentTypes.LightningLike;
break;
default:
return false;
}
}
paymentMethodId = new PaymentMethodId(parts[0], type);
return true;
}
public static PaymentMethodId Parse(string str)
{
var parts = str.Split('_');
return new PaymentMethodId(parts[0], parts.Length == 1 ? PaymentTypes.BTCLike : Enum.Parse<PaymentTypes>(parts[1]));
if (!TryParse(str, out var result))
throw new FormatException("Invalid PaymentMethodId");
return result;
}
}
}

@ -10,7 +10,7 @@
<h1>Welcome to BTCPay Server</h1>
<hr />
<p>BTCPay Server is a free and open source server for merchants wanting to accept Bitcoin for their business.</p>
<a style="background-color: #fff;color: #222;display:inline-block;text-align: center;white-space: nowrap;vertical-align: middle;user-select: none;line-height: 1.25;font-size: 1rem;text-decoration:none;font-weight: 700; text-transform: uppercase;border: none;border-radius: 300px;padding: 15px 30px;" href="https://docs.btcpayserver.org">Getting started</a>
<a style="background-color: #fff;color: #222;display:inline-block;text-align: center;white-space: nowrap;vertical-align: middle;user-select: none;line-height: 1.25;font-size: 1rem;text-decoration:none;font-weight: 700; text-transform: uppercase;border: none;border-radius: 300px;padding: 15px 30px;" href="https://btcpayserver.org" target="_blank">Official website</a>
</div>
</div>
</header>
@ -128,13 +128,19 @@
</div>
</div>
<div class="row">
<div class="col-lg-4 ml-auto text-center">
<a href="http://slack.forkbitpay.ninja/">
<div class="col-lg-3 ml-auto text-center">
<a href="https://chat.btcpayserver.org/">
<img src="~/img/mattermost.png" height="100" />
</a>
<p><a href="https://chat.btcpayserver.org/">On Mattermost</a></p>
</div>
<div class="col-lg-3 ml-auto text-center">
<a href="https://slack.btcpayserver.org/">
<img src="~/img/slack.png" height="100" />
</a>
<p><a href="http://slack.forkbitpay.ninja/">On Slack</a></p>
</div>
<div class="col-lg-4 mr-auto text-center">
<div class="col-lg-3 mr-auto text-center">
<a href="https://twitter.com/BtcpayServer">
<img src="~/img/twitter.png" height="100" />
</a>
@ -142,7 +148,7 @@
<a href="https://twitter.com/BtcpayServer">On Twitter</a>
</p>
</div>
<div class="col-lg-4 mr-auto text-center">
<div class="col-lg-3 mr-auto text-center">
<a href="https://github.com/btcpayserver/btcpayserver">
<img src="~/img/github.png" height="100" />
</a>

Binary file not shown.

After

(image error) Size: 26 KiB

@ -44,7 +44,7 @@
"Node Info": "Informácia o uzle",
"txCount": "{{count}} transakcia",
"txCount_plural": "{{count}} transakcií",
"Pay with CoinSwitch": "Pay with CoinSwitch",
"Pay with Changelly": "Pay with Changelly",
"Close": "Close"
"Pay with CoinSwitch": "Zaplatiť cez CoinSwitch",
"Pay with Changelly": "Zaplatiť cez Changelly",
"Close": "Zatvoriť"
}

@ -61,7 +61,7 @@ Altcoins are maintained by their respective communities.
## Documentation
Please check out our [complete documentation](https://github.com/btcpayserver/btcpayserver-doc) and [FAQ](https://github.com/btcpayserver/btcpayserver-doc/tree/master/FAQ#btcpay-frequently-asked-questions-and-common-issues) for more details.
Please check out our [official website](https://btcpayserver.org/), our [complete documentation](https://github.com/btcpayserver/btcpayserver-doc) and [FAQ](https://github.com/btcpayserver/btcpayserver-doc/tree/master/FAQ#btcpay-frequently-asked-questions-and-common-issues) for more details.
If you have any troubles with BTCPay, please file a [Github issue](https://github.com/btcpayserver/btcpayserver/issues).
For general questions, please join the community chat on [Mattermost](https://chat.btcpayserver.org/).