mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 22:11:48 +01:00
Big refactorying for supporting multi currencies
This commit is contained in:
parent
d5ef36fe50
commit
a37fdde214
23 changed files with 1201 additions and 240 deletions
|
@ -38,33 +38,38 @@ namespace BTCPayServer.Tests
|
|||
public void CanCalculateCryptoDue()
|
||||
{
|
||||
var entity = new InvoiceEntity();
|
||||
#pragma warning disable CS0618
|
||||
entity.TxFee = Money.Coins(0.1m);
|
||||
entity.Rate = 5000;
|
||||
|
||||
var cryptoData = entity.GetCryptoData("BTC");
|
||||
Assert.NotNull(cryptoData); // Should use legacy data to build itself
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
|
||||
Assert.Equal(Money.Coins(1.1m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.1m), entity.GetTotalCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.1m), cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.1m), cryptoData.GetTotalCryptoDue());
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()), Accounted = true });
|
||||
|
||||
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
||||
Assert.Equal(Money.Coins(0.7m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.2m), entity.GetTotalCryptoDue());
|
||||
Assert.Equal(Money.Coins(0.7m), cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.2m), cryptoData.GetTotalCryptoDue());
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
Assert.Equal(Money.Coins(0.6m), entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
Assert.Equal(Money.Coins(0.6m), cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), cryptoData.GetTotalCryptoDue());
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()), Accounted = true });
|
||||
|
||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
Assert.Equal(Money.Zero, cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), cryptoData.GetTotalCryptoDue());
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
|
||||
Assert.Equal(Money.Zero, entity.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), entity.GetTotalCryptoDue());
|
||||
Assert.Equal(Money.Zero, cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), cryptoData.GetTotalCryptoDue());
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -312,26 +317,26 @@ namespace BTCPayServer.Tests
|
|||
StoreId = user.StoreId,
|
||||
TextSearch = invoice.OrderId
|
||||
}).GetAwaiter().GetResult();
|
||||
Assert.Equal(1, textSearchResult.Length);
|
||||
Assert.Single(textSearchResult);
|
||||
textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
TextSearch = invoice.Id
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
Assert.Equal(1, textSearchResult.Length);
|
||||
Assert.Single(textSearchResult);
|
||||
});
|
||||
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal(Money.Coins(0), invoice.BtcPaid);
|
||||
Assert.Equal("new", invoice.Status);
|
||||
Assert.Equal(false, (bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
Assert.False((bool)((JValue)invoice.ExceptionStatus).Value);
|
||||
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime).Length);
|
||||
Assert.Equal(0, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime + TimeSpan.FromDays(1)).Length);
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5)).Length);
|
||||
Assert.Equal(1, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime).Length);
|
||||
Assert.Equal(0, user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime - TimeSpan.FromDays(1)).Length);
|
||||
Assert.Single(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime));
|
||||
Assert.Empty(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime + TimeSpan.FromDays(1)));
|
||||
Assert.Single(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5)));
|
||||
Assert.Single(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime));
|
||||
Assert.Empty(user.BitPay.GetInvoices(invoice.InvoiceTime.DateTime - TimeSpan.FromDays(5), invoice.InvoiceTime.DateTime - TimeSpan.FromDays(1)));
|
||||
|
||||
|
||||
var firstPayment = Money.Coins(0.04m);
|
||||
|
@ -348,7 +353,7 @@ namespace BTCPayServer.Tests
|
|||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
Assert.Equal(1, invoiceEntity.HistoricalAddresses.Length);
|
||||
Assert.Single(invoiceEntity.HistoricalAddresses);
|
||||
Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned);
|
||||
|
||||
Money secondPayment = Money.Zero;
|
||||
|
@ -360,7 +365,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal("new", localInvoice.Status);
|
||||
Assert.Equal(firstPayment, localInvoice.BtcPaid);
|
||||
txFee = localInvoice.BtcDue - invoice.BtcDue;
|
||||
Assert.Equal("paidPartial", localInvoice.ExceptionStatus);
|
||||
Assert.Equal("paidPartial", localInvoice.ExceptionStatus.ToString());
|
||||
Assert.NotEqual(localInvoice.BitcoinAddress, invoice.BitcoinAddress); //New address
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
|
|
16
BTCPayServer/BTCPayNetwork.cs
Normal file
16
BTCPayServer/BTCPayNetwork.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class BTCPayNetwork
|
||||
{
|
||||
public Network NBitcoinNetwork { get; set; }
|
||||
public string CryptoCode { get; internal set; }
|
||||
public string BlockExplorerLink { get; internal set; }
|
||||
public string UriScheme { get; internal set; }
|
||||
}
|
||||
}
|
59
BTCPayServer/BTCPayNetworkProvider.cs
Normal file
59
BTCPayServer/BTCPayNetworkProvider.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class BTCPayNetworkProvider
|
||||
{
|
||||
Dictionary<string, BTCPayNetwork> _Networks = new Dictionary<string, BTCPayNetwork>();
|
||||
public BTCPayNetworkProvider(Network network)
|
||||
{
|
||||
if(network == Network.Main)
|
||||
{
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
BlockExplorerLink = "https://www.smartbit.com.au/tx/{0}",
|
||||
NBitcoinNetwork = Network.Main,
|
||||
UriScheme = "bitcoin"
|
||||
});
|
||||
}
|
||||
|
||||
if (network == Network.TestNet)
|
||||
{
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
|
||||
NBitcoinNetwork = Network.TestNet,
|
||||
UriScheme = "bitcoin"
|
||||
});
|
||||
}
|
||||
|
||||
if (network == Network.RegTest)
|
||||
{
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
|
||||
NBitcoinNetwork = Network.RegTest,
|
||||
UriScheme = "bitcoin"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(BTCPayNetwork network)
|
||||
{
|
||||
_Networks.Add(network.CryptoCode, network);
|
||||
}
|
||||
|
||||
public BTCPayNetwork GetNetwork(string cryptoCode)
|
||||
{
|
||||
_Networks.TryGetValue(cryptoCode.ToUpperInvariant(), out BTCPayNetwork network);
|
||||
return network;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,10 +43,10 @@ namespace BTCPayServer.Controllers
|
|||
EventAggregator eventAggregator,
|
||||
BTCPayServerOptions options,
|
||||
IServer server,
|
||||
Network network)
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
_Settings = repo;
|
||||
_Network = network;
|
||||
_Network = networkProvider.GetNetwork("BTC").NBitcoinNetwork;
|
||||
_Explorer = explorer;
|
||||
_Options = options;
|
||||
_EventAggregator = eventAggregator;
|
||||
|
|
|
@ -24,16 +24,19 @@ namespace BTCPayServer.Controllers
|
|||
private InvoiceRepository _InvoiceRepository;
|
||||
private TokenRepository _TokenRepository;
|
||||
private StoreRepository _StoreRepository;
|
||||
private BTCPayNetworkProvider _NetworkProvider;
|
||||
|
||||
public InvoiceControllerAPI(InvoiceController invoiceController,
|
||||
InvoiceRepository invoceRepository,
|
||||
TokenRepository tokenRepository,
|
||||
StoreRepository storeRepository)
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
this._InvoiceController = invoiceController;
|
||||
this._InvoiceRepository = invoceRepository;
|
||||
this._TokenRepository = tokenRepository;
|
||||
this._StoreRepository = storeRepository;
|
||||
this._NetworkProvider = networkProvider;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
@ -56,7 +59,7 @@ namespace BTCPayServer.Controllers
|
|||
if (invoice == null)
|
||||
throw new BitpayHttpException(404, "Object not found");
|
||||
|
||||
var resp = invoice.EntityToDTO();
|
||||
var resp = invoice.EntityToDTO(_NetworkProvider);
|
||||
return new DataWrapper<InvoiceResponse>(resp);
|
||||
}
|
||||
|
||||
|
@ -90,7 +93,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
|
||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||
.Select((o) => o.EntityToDTO()).ToArray();
|
||||
.Select((o) => o.EntityToDTO(_NetworkProvider)).ToArray();
|
||||
|
||||
return DataWrapper.Create(entities);
|
||||
}
|
||||
|
|
|
@ -17,21 +17,25 @@ namespace BTCPayServer.Controllers
|
|||
[HttpGet]
|
||||
[Route("i/{invoiceId}")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
|
||||
public async Task<IActionResult> GetInvoiceRequest(string invoiceId)
|
||||
public async Task<IActionResult> GetInvoiceRequest(string invoiceId, string cryptoCode = null)
|
||||
{
|
||||
if (cryptoCode == null)
|
||||
cryptoCode = "BTC";
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null || invoice.IsExpired())
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (invoice == null || invoice.IsExpired() || network == null || !invoice.Support(network))
|
||||
return NotFound();
|
||||
|
||||
var dto = invoice.EntityToDTO();
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var cryptoData = dto.CryptoInfo.First(c => c.CryptoCode.Equals(cryptoCode, StringComparison.OrdinalIgnoreCase));
|
||||
PaymentRequest request = new PaymentRequest
|
||||
{
|
||||
DetailsVersion = 1
|
||||
};
|
||||
request.Details.Expires = invoice.ExpirationTime;
|
||||
request.Details.Memo = invoice.ProductInformation.ItemDesc;
|
||||
request.Details.Network = _Network;
|
||||
request.Details.Outputs.Add(new PaymentOutput() { Amount = dto.BTCDue, Script = BitcoinAddress.Create(dto.BitcoinAddress, _Network).ScriptPubKey });
|
||||
request.Details.Network = network.NBitcoinNetwork;
|
||||
request.Details.Outputs.Add(new PaymentOutput() { Amount = cryptoData.Due, Script = BitcoinAddress.Create(cryptoData.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);
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
[HttpPost]
|
||||
[Route("invoices/{invoiceId}")]
|
||||
public IActionResult Invoice(string invoiceId, string command)
|
||||
public IActionResult Invoice(string invoiceId, string command, string cryptoCode = null)
|
||||
{
|
||||
if (command == "refresh")
|
||||
{
|
||||
|
@ -41,8 +41,10 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("invoices/{invoiceId}")]
|
||||
public async Task<IActionResult> Invoice(string invoiceId)
|
||||
public async Task<IActionResult> Invoice(string invoiceId, string cryptoCode = null)
|
||||
{
|
||||
if (cryptoCode == null)
|
||||
cryptoCode = "BTC";
|
||||
var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
UserId = GetUserId(),
|
||||
|
@ -51,7 +53,14 @@ namespace BTCPayServer.Controllers
|
|||
if (invoice == null)
|
||||
return NotFound();
|
||||
|
||||
var dto = invoice.EntityToDTO();
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (network == null || !invoice.Support(network))
|
||||
return NotFound();
|
||||
|
||||
var cryptoData = invoice.GetCryptoData(network);
|
||||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.CryptoCode.Equals(cryptoCode, StringComparison.OrdinalIgnoreCase));
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
InvoiceDetailsModel model = new InvoiceDetailsModel()
|
||||
{
|
||||
|
@ -64,16 +73,16 @@ namespace BTCPayServer.Controllers
|
|||
ExpirationDate = invoice.ExpirationTime,
|
||||
OrderId = invoice.OrderId,
|
||||
BuyerInformation = invoice.BuyerInformation,
|
||||
Rate = invoice.Rate,
|
||||
Rate = cryptoData.Rate,
|
||||
Fiat = dto.Price + " " + dto.Currency,
|
||||
BTC = invoice.GetTotalCryptoDue().ToString() + " BTC",
|
||||
BTCDue = invoice.GetCryptoDue().ToString() + " BTC",
|
||||
BTCPaid = invoice.GetTotalPaid().ToString() + " BTC",
|
||||
NetworkFee = invoice.GetNetworkFee().ToString() + " BTC",
|
||||
BTC = cryptoData.GetTotalCryptoDue().ToString() + $" {network.CryptoCode}",
|
||||
BTCDue = cryptoData.GetCryptoDue().ToString() + $" {network.CryptoCode}",
|
||||
BTCPaid = cryptoData.GetTotalPaid().ToString() + $" {network.CryptoCode}",
|
||||
NetworkFee = cryptoData.GetNetworkFee().ToString() + $" {network.CryptoCode}",
|
||||
NotificationUrl = invoice.NotificationURL,
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
BitcoinAddress = invoice.DepositAddress,
|
||||
PaymentUrl = dto.PaymentUrls.BIP72
|
||||
BitcoinAddress = BitcoinAddress.Create(cryptoInfo.Address, network.NBitcoinNetwork),
|
||||
PaymentUrl = cryptoInfo.PaymentUrls.BIP72
|
||||
};
|
||||
|
||||
var payments = invoice
|
||||
|
@ -81,11 +90,11 @@ namespace BTCPayServer.Controllers
|
|||
.Select(async payment =>
|
||||
{
|
||||
var m = new InvoiceDetailsModel.Payment();
|
||||
m.DepositAddress = payment.Output.ScriptPubKey.GetDestinationAddress(_Network);
|
||||
m.DepositAddress = payment.GetScriptPubKey().GetDestinationAddress(network.NBitcoinNetwork);
|
||||
m.Confirmations = (await _Explorer.GetTransactionAsync(payment.Outpoint.Hash))?.Confirmations ?? 0;
|
||||
m.TransactionId = payment.Outpoint.Hash.ToString();
|
||||
m.ReceivedTime = payment.ReceivedTime;
|
||||
m.TransactionLink = _Network == Network.Main ? $"https://www.smartbit.com.au/tx/{m.TransactionId}" : $"https://testnet.smartbit.com.au/tx/{m.TransactionId}";
|
||||
m.TransactionLink = string.Format(network.BlockExplorerLink, m.TransactionId);
|
||||
return m;
|
||||
})
|
||||
.ToArray();
|
||||
|
@ -100,48 +109,54 @@ namespace BTCPayServer.Controllers
|
|||
[Route("invoice")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null)
|
||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null, string cryptoCode = null)
|
||||
{
|
||||
if (cryptoCode == null)
|
||||
cryptoCode = "BTC";
|
||||
//Keep compatibility with Bitpay
|
||||
invoiceId = invoiceId ?? id;
|
||||
id = invoiceId;
|
||||
////
|
||||
|
||||
var model = await GetInvoiceModel(invoiceId);
|
||||
var model = await GetInvoiceModel(invoiceId, cryptoCode);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
return View(nameof(Checkout), model);
|
||||
}
|
||||
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId)
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, string cryptoCode)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null)
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (invoice == null || network == null || !invoice.Support(network))
|
||||
return null;
|
||||
|
||||
var cryptoData = invoice.GetCryptoData(network);
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var dto = invoice.EntityToDTO();
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.CryptoCode == network.CryptoCode);
|
||||
var currency = invoice.ProductInformation.Currency;
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
||||
OrderId = invoice.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
BtcAddress = invoice.DepositAddress.ToString(),
|
||||
BtcAmount = (invoice.GetTotalCryptoDue() - invoice.TxFee).ToString(),
|
||||
BtcTotalDue = invoice.GetTotalCryptoDue().ToString(),
|
||||
BtcDue = invoice.GetCryptoDue().ToString(),
|
||||
BtcAddress = cryptoData.DepositAddress,
|
||||
BtcAmount = (cryptoData.GetTotalCryptoDue() - cryptoData.TxFee).ToString(),
|
||||
BtcTotalDue = cryptoData.GetTotalCryptoDue().ToString(),
|
||||
BtcDue = cryptoData.GetCryptoDue().ToString(),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
Rate = invoice.Rate.ToString("C", _CurrencyNameTable.GetCurrencyProvider(currency)) + $" ({currency})",
|
||||
Rate = cryptoData.Rate.ToString("C", _CurrencyNameTable.GetCurrencyProvider(currency)) + $" ({currency})",
|
||||
MerchantRefLink = invoice.RedirectURL ?? "/",
|
||||
StoreName = store.StoreName,
|
||||
TxFees = invoice.TxFee.ToString(),
|
||||
InvoiceBitcoinUrl = dto.PaymentUrls.BIP72,
|
||||
TxCount = invoice.GetTxCount(),
|
||||
BtcPaid = invoice.GetTotalPaid().ToString(),
|
||||
TxFees = cryptoData.TxFee.ToString(),
|
||||
InvoiceBitcoinUrl = cryptoInfo.PaymentUrls.BIP72,
|
||||
TxCount = cryptoData.GetTxCount(),
|
||||
BtcPaid = cryptoData.GetTotalPaid().ToString(),
|
||||
Status = invoice.Status
|
||||
};
|
||||
|
||||
|
@ -163,9 +178,9 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/status")]
|
||||
public async Task<IActionResult> GetStatus(string invoiceId)
|
||||
public async Task<IActionResult> GetStatus(string invoiceId, string cryptoCode)
|
||||
{
|
||||
var model = await GetInvoiceModel(invoiceId);
|
||||
var model = await GetInvoiceModel(invoiceId, cryptoCode);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
return Json(model);
|
||||
|
|
|
@ -48,15 +48,13 @@ namespace BTCPayServer.Controllers
|
|||
IRateProvider _RateProvider;
|
||||
private InvoiceWatcher _Watcher;
|
||||
StoreRepository _StoreRepository;
|
||||
Network _Network;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
IFeeProvider _FeeProvider;
|
||||
IFeeProviderFactory _FeeProviderFactory;
|
||||
private CurrencyNameTable _CurrencyNameTable;
|
||||
ExplorerClient _Explorer;
|
||||
EventAggregator _EventAggregator;
|
||||
public InvoiceController(
|
||||
Network network,
|
||||
InvoiceRepository invoiceRepository,
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
public InvoiceController(InvoiceRepository invoiceRepository,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
BTCPayWallet wallet,
|
||||
|
@ -65,31 +63,32 @@ namespace BTCPayServer.Controllers
|
|||
EventAggregator eventAggregator,
|
||||
InvoiceWatcherAccessor watcher,
|
||||
ExplorerClient explorerClient,
|
||||
IFeeProvider feeProvider)
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
IFeeProviderFactory feeProviderFactory)
|
||||
{
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
_Explorer = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
|
||||
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
||||
_Network = network ?? throw new ArgumentNullException(nameof(network));
|
||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||
_Watcher = (watcher ?? throw new ArgumentNullException(nameof(watcher))).Instance;
|
||||
_UserManager = userManager;
|
||||
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
||||
_FeeProviderFactory = feeProviderFactory ?? throw new ArgumentNullException(nameof(feeProviderFactory));
|
||||
_EventAggregator = eventAggregator;
|
||||
_NetworkProvider = networkProvider;
|
||||
}
|
||||
|
||||
|
||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15)
|
||||
{
|
||||
//TODO: expiryMinutes (time before a new invoice can become paid) and monitoringMinutes (time before a paid invoice becomes invalid) should be configurable at store level
|
||||
var derivationStrategy = store.DerivationStrategy;
|
||||
var entity = new InvoiceEntity
|
||||
{
|
||||
InvoiceTime = DateTimeOffset.UtcNow,
|
||||
DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy")
|
||||
};
|
||||
var storeBlob = store.GetStoreBlob(_Network);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null;
|
||||
if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ?
|
||||
notificationUri = null;
|
||||
|
@ -114,16 +113,45 @@ namespace BTCPayServer.Controllers
|
|||
entity.Status = "new";
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
|
||||
var getFeeRate = _FeeProvider.GetFeeRateAsync();
|
||||
var getRate = _RateProvider.GetRateAsync(invoice.Currency);
|
||||
var getAddress = _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy));
|
||||
entity.TxFee = storeBlob.NetworkFeeDisabled ? Money.Zero : (await getFeeRate).GetFee(100); // assume price for 100 bytes
|
||||
entity.Rate = (double)await getRate;
|
||||
var queries = storeBlob.GetSupportedCryptoCurrencies()
|
||||
.Select(n => _NetworkProvider.GetNetwork(n))
|
||||
.Where(n => n != null)
|
||||
.Select(network =>
|
||||
{
|
||||
return new
|
||||
{
|
||||
network = network,
|
||||
getFeeRate = _FeeProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(),
|
||||
getRate = _RateProvider.GetRateAsync(invoice.Currency),
|
||||
getAddress = _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy, network))
|
||||
};
|
||||
});
|
||||
|
||||
var cryptoDatas = new Dictionary<string, CryptoData>();
|
||||
foreach (var q in queries)
|
||||
{
|
||||
CryptoData cryptoData = new CryptoData();
|
||||
cryptoData.CryptoCode = q.network.CryptoCode;
|
||||
cryptoData.FeeRate = (await q.getFeeRate);
|
||||
cryptoData.TxFee = storeBlob.NetworkFeeDisabled ? Money.Zero : cryptoData.FeeRate.GetFee(100); // assume price for 100 bytes
|
||||
cryptoData.Rate = await q.getRate;
|
||||
cryptoData.DepositAddress = (await q.getAddress).ToString();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
if (q.network.CryptoCode == "BTC")
|
||||
{
|
||||
entity.TxFee = cryptoData.TxFee;
|
||||
entity.Rate = cryptoData.Rate;
|
||||
entity.DepositAddress = cryptoData.DepositAddress;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
cryptoDatas.Add(cryptoData.CryptoCode, cryptoData);
|
||||
}
|
||||
entity.SetCryptoData(cryptoDatas);
|
||||
entity.PosData = invoice.PosData;
|
||||
entity.DepositAddress = await getAddress;
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
|
||||
_Watcher.Watch(entity.Id);
|
||||
var resp = entity.EntityToDTO();
|
||||
var resp = entity.EntityToDTO(_NetworkProvider);
|
||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||
}
|
||||
|
||||
|
@ -155,9 +183,9 @@ namespace BTCPayServer.Controllers
|
|||
buyerInformation.BuyerZip = buyerInformation.BuyerZip ?? buyer.zip;
|
||||
}
|
||||
|
||||
private DerivationStrategyBase ParseDerivationStrategy(string derivationStrategy)
|
||||
private DerivationStrategyBase ParseDerivationStrategy(string derivationStrategy, BTCPayNetwork network)
|
||||
{
|
||||
return new DerivationStrategyFactory(_Network).Parse(derivationStrategy);
|
||||
return new DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivationStrategy);
|
||||
}
|
||||
|
||||
private TDest Map<TFrom, TDest>(TFrom data)
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace BTCPayServer.Controllers
|
|||
UserManager<ApplicationUser> userManager,
|
||||
AccessTokenController tokenController,
|
||||
BTCPayWallet wallet,
|
||||
Network network,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
IHostingEnvironment env)
|
||||
{
|
||||
_Repo = repo;
|
||||
|
@ -43,7 +43,7 @@ namespace BTCPayServer.Controllers
|
|||
_TokenController = tokenController;
|
||||
_Wallet = wallet;
|
||||
_Env = env;
|
||||
_Network = network;
|
||||
_Network = networkProvider.GetNetwork("BTC").NBitcoinNetwork;
|
||||
_CallbackController = callbackController;
|
||||
}
|
||||
Network _Network;
|
||||
|
@ -145,7 +145,7 @@ namespace BTCPayServer.Controllers
|
|||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var storeBlob = store.GetStoreBlob(_Network);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new StoreViewModel();
|
||||
vm.Id = store.Id;
|
||||
vm.StoreName = store.StoreName;
|
||||
|
@ -210,11 +210,11 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
var blob = store.GetStoreBlob(_Network);
|
||||
var blob = store.GetStoreBlob();
|
||||
blob.NetworkFeeDisabled = !model.NetworkFee;
|
||||
blob.MonitoringExpiration = model.MonitoringExpiration;
|
||||
|
||||
if (store.SetStoreBlob(blob, _Network))
|
||||
if (store.SetStoreBlob(blob))
|
||||
{
|
||||
needUpdate = true;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,17 @@ namespace BTCPayServer.Data
|
|||
get; set;
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use GetCryptoCode instead")]
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
#pragma warning disable CS0618
|
||||
public string GetCryptoCode()
|
||||
{
|
||||
return string.IsNullOrEmpty(CryptoCode) ? "BTC" : CryptoCode;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
public DateTimeOffset Assigned
|
||||
{
|
||||
get; set;
|
||||
|
|
|
@ -62,15 +62,17 @@ namespace BTCPayServer.Data
|
|||
set;
|
||||
}
|
||||
|
||||
public StoreBlob GetStoreBlob(Network network)
|
||||
static Network Dummy = Network.Main;
|
||||
|
||||
public StoreBlob GetStoreBlob()
|
||||
{
|
||||
return StoreBlob == null ? new StoreBlob() : new Serializer(network).ToObject<StoreBlob>(Encoding.UTF8.GetString(StoreBlob));
|
||||
return StoreBlob == null ? new StoreBlob() : new Serializer(Dummy).ToObject<StoreBlob>(Encoding.UTF8.GetString(StoreBlob));
|
||||
}
|
||||
|
||||
public bool SetStoreBlob(StoreBlob storeBlob, Network network)
|
||||
public bool SetStoreBlob(StoreBlob storeBlob)
|
||||
{
|
||||
var original = new Serializer(network).ToString(GetStoreBlob(network));
|
||||
var newBlob = new Serializer(network).ToString(storeBlob);
|
||||
var original = new Serializer(Dummy).ToString(GetStoreBlob());
|
||||
var newBlob = new Serializer(Dummy).ToString(storeBlob);
|
||||
if (original == newBlob)
|
||||
return false;
|
||||
StoreBlob = Encoding.UTF8.GetBytes(newBlob);
|
||||
|
@ -95,5 +97,19 @@ namespace BTCPayServer.Data
|
|||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Obsolete("Use GetSupportedCryptoCurrencies() instead")]
|
||||
public string[] SupportedCryptoCurrencies { get; set; }
|
||||
|
||||
public string[] GetSupportedCryptoCurrencies()
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
if(SupportedCryptoCurrencies == null)
|
||||
{
|
||||
return new string[] { "BTC" };
|
||||
}
|
||||
return SupportedCryptoCurrencies;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,7 +107,6 @@ namespace BTCPayServer.Hosting
|
|||
services.AddSingleton<BTCPayServerEnvironment>();
|
||||
services.TryAddSingleton<TokenRepository>();
|
||||
services.TryAddSingleton<EventAggregator>();
|
||||
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
|
||||
services.TryAddSingleton<ApplicationDbContextFactory>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
|
@ -125,14 +124,20 @@ namespace BTCPayServer.Hosting
|
|||
}
|
||||
return dbContext;
|
||||
});
|
||||
|
||||
services.TryAddSingleton<BTCPayNetworkProvider>(o =>
|
||||
{
|
||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||
return new BTCPayNetworkProvider(opts.Network);
|
||||
});
|
||||
|
||||
services.TryAddSingleton<StoreRepository>();
|
||||
services.TryAddSingleton<BTCPayWallet>();
|
||||
services.TryAddSingleton<CurrencyNameTable>();
|
||||
services.TryAddSingleton<IFeeProvider>(o => new NBXplorerFeeProvider()
|
||||
services.TryAddSingleton<IFeeProviderFactory>(o => new NBXplorerFeeProviderFactory(o.GetRequiredService<ExplorerClient>())
|
||||
{
|
||||
Fallback = new FeeRate(100, 1),
|
||||
BlockTarget = 20,
|
||||
ExplorerClient = o.GetRequiredService<ExplorerClient>()
|
||||
BlockTarget = 20
|
||||
});
|
||||
|
||||
services.TryAddSingleton<NBXplorerWaiterAccessor>();
|
||||
|
|
481
BTCPayServer/Migrations/20171221054550_AltcoinSupport.Designer.cs
generated
Normal file
481
BTCPayServer/Migrations/20171221054550_AltcoinSupport.Designer.cs
generated
Normal file
|
@ -0,0 +1,481 @@
|
|||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20171221054550_AltcoinSupport")]
|
||||
partial class AltcoinSupport
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("Address");
|
||||
|
||||
b.Property<DateTimeOffset>("Assigned");
|
||||
|
||||
b.Property<string>("CryptoCode");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
||||
b.ToTable("HistoricalAddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("CustomerEmail");
|
||||
|
||||
b.Property<string>("ExceptionStatus");
|
||||
|
||||
b.Property<string>("ItemCode");
|
||||
|
||||
b.Property<string>("OrderId");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<DateTimeOffset>("PairingTime");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SIN");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PairedSINData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("DateCreated");
|
||||
|
||||
b.Property<DateTimeOffset>("Expiration");
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("TokenValue");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PairingCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Accounted");
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("RefundAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("DerivationStrategy");
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
|
||||
b.Property<string>("StoreWebsite");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<bool>("RequiresEmailConfirmation");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData")
|
||||
.WithMany("HistoricalAddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany()
|
||||
.HasForeignKey("StoreDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("RefundAddresses")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
24
BTCPayServer/Migrations/20171221054550_AltcoinSupport.cs
Normal file
24
BTCPayServer/Migrations/20171221054550_AltcoinSupport.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class AltcoinSupport : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CryptoCode",
|
||||
table: "HistoricalAddressInvoices",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CryptoCode",
|
||||
table: "HistoricalAddressInvoices");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ namespace BTCPayServer.Migrations
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
|
@ -44,6 +44,8 @@ namespace BTCPayServer.Migrations
|
|||
|
||||
b.Property<DateTimeOffset>("Assigned");
|
||||
|
||||
b.Property<string>("CryptoCode");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
@ -382,7 +384,7 @@ namespace BTCPayServer.Migrations
|
|||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany()
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
|
|
|
@ -36,11 +36,56 @@ namespace BTCPayServer.Models
|
|||
}
|
||||
}
|
||||
|
||||
public class InvoiceCryptoInfo
|
||||
{
|
||||
[JsonProperty("cryptoCode")]
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
[JsonProperty("rate")]
|
||||
public decimal Rate { get; set; }
|
||||
|
||||
//"exRates":{"USD":4320.02}
|
||||
[JsonProperty("exRates")]
|
||||
public Dictionary<string, double> ExRates
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
//"btcPaid":"0.000000"
|
||||
[JsonProperty("paid")]
|
||||
public string Paid
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
//"btcPrice":"0.001157"
|
||||
[JsonProperty("price")]
|
||||
public string Price
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
//"btcDue":"0.001160"
|
||||
[JsonProperty("due")]
|
||||
public string Due
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public NBitpayClient.InvoicePaymentUrls PaymentUrls
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Address { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
//{"facade":"pos/invoice","data":{,}}
|
||||
public class InvoiceResponse
|
||||
{
|
||||
//"url":"https://test.bitpay.com/invoice?id=9saCHtp1zyPcNoi3rDdBu8"
|
||||
[JsonProperty("url")]
|
||||
[Obsolete("Use CryptoInfo.Url instead")]
|
||||
public string Url
|
||||
{
|
||||
get; set;
|
||||
|
@ -59,6 +104,7 @@ namespace BTCPayServer.Models
|
|||
}
|
||||
//"btcPrice":"0.001157"
|
||||
[JsonProperty("btcPrice")]
|
||||
[Obsolete("Use CryptoInfo.Price instead")]
|
||||
public string BTCPrice
|
||||
{
|
||||
get; set;
|
||||
|
@ -66,11 +112,15 @@ namespace BTCPayServer.Models
|
|||
|
||||
//"btcDue":"0.001160"
|
||||
[JsonProperty("btcDue")]
|
||||
[Obsolete("Use CryptoInfo.Due instead")]
|
||||
public string BTCDue
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty("cryptoInfo")]
|
||||
public List<InvoiceCryptoInfo> CryptoInfo { get; set; }
|
||||
|
||||
//"price":5
|
||||
[JsonProperty("price")]
|
||||
public double Price
|
||||
|
@ -87,6 +137,7 @@ namespace BTCPayServer.Models
|
|||
|
||||
//"exRates":{"USD":4320.02}
|
||||
[JsonProperty("exRates")]
|
||||
[Obsolete("Use CryptoInfo.ExRates instead")]
|
||||
public Dictionary<string, double> ExRates
|
||||
{
|
||||
get; set;
|
||||
|
@ -156,6 +207,7 @@ namespace BTCPayServer.Models
|
|||
|
||||
//"btcPaid":"0.000000"
|
||||
[JsonProperty("btcPaid")]
|
||||
[Obsolete("Use CryptoInfo.Paid instead")]
|
||||
public string BTCPaid
|
||||
{
|
||||
get; set;
|
||||
|
@ -163,7 +215,8 @@ namespace BTCPayServer.Models
|
|||
|
||||
//"rate":4320.02
|
||||
[JsonProperty("rate")]
|
||||
public double Rate
|
||||
[Obsolete("Use CryptoInfo.Rate instead")]
|
||||
public decimal Rate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
@ -178,6 +231,7 @@ namespace BTCPayServer.Models
|
|||
|
||||
//"paymentUrls":{"BIP21":"bitcoin:muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv?amount=0.001160","BIP72":"bitcoin:muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv?amount=0.001160&r=https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8","BIP72b":"bitcoin:?r=https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8","BIP73":"https://test.bitpay.com/i/9saCHtp1zyPcNoi3rDdBu8"}
|
||||
[JsonProperty("paymentUrls")]
|
||||
[Obsolete("Use CryptoInfo.PaymentsUrls instead")]
|
||||
public NBitpayClient.InvoicePaymentUrls PaymentUrls
|
||||
{
|
||||
get; set;
|
||||
|
@ -197,6 +251,7 @@ namespace BTCPayServer.Models
|
|||
|
||||
//"bitcoinAddress":"muFQCEbfRJohcds3bkfv1sRFj8uVTfv2wv"
|
||||
[JsonProperty("bitcoinAddress")]
|
||||
[Obsolete("Use CryptoInfo.Address instead")]
|
||||
public string BitcoinAddress
|
||||
{
|
||||
get; set;
|
||||
|
|
|
@ -92,7 +92,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
get;
|
||||
set;
|
||||
}
|
||||
public double Rate
|
||||
public decimal Rate
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
|
|
12
BTCPayServer/Services/Fees/IFeeProviderFactory.cs
Normal file
12
BTCPayServer/Services/Fees/IFeeProviderFactory.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
public interface IFeeProviderFactory
|
||||
{
|
||||
IFeeProvider CreateFeeProvider(BTCPayNetwork network);
|
||||
}
|
||||
}
|
|
@ -8,29 +8,49 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace BTCPayServer.Services.Fees
|
||||
{
|
||||
public class NBXplorerFeeProvider : IFeeProvider
|
||||
public class NBXplorerFeeProviderFactory : IFeeProviderFactory
|
||||
{
|
||||
public NBXplorerFeeProviderFactory(ExplorerClient explorerClient)
|
||||
{
|
||||
if (explorerClient == null)
|
||||
throw new ArgumentNullException(nameof(explorerClient));
|
||||
_ExplorerClient = explorerClient;
|
||||
}
|
||||
|
||||
private readonly ExplorerClient _ExplorerClient;
|
||||
public ExplorerClient ExplorerClient
|
||||
{
|
||||
get; set;
|
||||
get
|
||||
{
|
||||
return _ExplorerClient;
|
||||
}
|
||||
}
|
||||
public FeeRate Fallback
|
||||
|
||||
public FeeRate Fallback { get; set; }
|
||||
public int BlockTarget { get; set; }
|
||||
public IFeeProvider CreateFeeProvider(BTCPayNetwork network)
|
||||
{
|
||||
get; set;
|
||||
return new NBXplorerFeeProvider(this);
|
||||
}
|
||||
public int BlockTarget
|
||||
}
|
||||
public class NBXplorerFeeProvider : IFeeProvider
|
||||
{
|
||||
public NBXplorerFeeProvider(NBXplorerFeeProviderFactory factory)
|
||||
{
|
||||
get; set;
|
||||
if (factory == null)
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
_Factory = factory;
|
||||
}
|
||||
private readonly NBXplorerFeeProviderFactory _Factory;
|
||||
public async Task<FeeRate> GetFeeRateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (await ExplorerClient.GetFeeRateAsync(BlockTarget).ConfigureAwait(false)).FeeRate;
|
||||
return (await _Factory.ExplorerClient.GetFeeRateAsync(_Factory.BlockTarget).ConfigureAwait(false)).FeeRate;
|
||||
}
|
||||
catch (NBXplorerException ex) when (ex.Error.HttpCode == 400 && ex.Error.Code == "fee-estimation-unavailable")
|
||||
{
|
||||
return Fallback;
|
||||
return _Factory.Fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ using Newtonsoft.Json.Linq;
|
|||
using NBitcoin.DataEncoders;
|
||||
using BTCPayServer.Data;
|
||||
using NBXplorer.Models;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
|
@ -82,7 +83,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
|
||||
[JsonProperty(PropertyName = "price")]
|
||||
public double Price
|
||||
public decimal Price
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
@ -111,65 +112,17 @@ namespace BTCPayServer.Services.Invoices
|
|||
get; set;
|
||||
}
|
||||
|
||||
public int GetTxCount()
|
||||
{
|
||||
return Calculate().TxCount;
|
||||
}
|
||||
|
||||
public string OrderId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Money GetTotalCryptoDue()
|
||||
{
|
||||
return Calculate().TotalDue;
|
||||
}
|
||||
|
||||
private (Money TotalDue, Money Paid, int TxCount) Calculate()
|
||||
{
|
||||
var totalDue = Money.Coins((decimal)(ProductInformation.Price / Rate)) + TxFee;
|
||||
var paid = Money.Zero;
|
||||
int txCount = 1;
|
||||
var payments =
|
||||
Payments
|
||||
.Where(p => p.Accounted)
|
||||
.OrderByDescending(p => p.ReceivedTime)
|
||||
.Select(_ =>
|
||||
{
|
||||
paid += _.Output.Value;
|
||||
return _;
|
||||
})
|
||||
.TakeWhile(_ =>
|
||||
{
|
||||
var paidEnough = totalDue <= paid;
|
||||
if (!paidEnough)
|
||||
{
|
||||
txCount++;
|
||||
totalDue += TxFee;
|
||||
}
|
||||
return !paidEnough;
|
||||
})
|
||||
.ToArray();
|
||||
return (totalDue, paid, txCount);
|
||||
}
|
||||
|
||||
public Money GetTotalPaid()
|
||||
{
|
||||
return Calculate().Paid;
|
||||
}
|
||||
public Money GetCryptoDue()
|
||||
{
|
||||
var o = Calculate();
|
||||
var v = o.TotalDue - o.Paid;
|
||||
return v < Money.Zero ? Money.Zero : v;
|
||||
}
|
||||
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public double Rate
|
||||
[Obsolete("Use GetCryptoData(network).Rate instead")]
|
||||
public decimal Rate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
@ -181,7 +134,9 @@ namespace BTCPayServer.Services.Invoices
|
|||
{
|
||||
get; set;
|
||||
}
|
||||
public BitcoinAddress DepositAddress
|
||||
|
||||
[Obsolete("Use GetCryptoData(network).DepositAddress instead")]
|
||||
public string DepositAddress
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
@ -231,6 +186,8 @@ namespace BTCPayServer.Services.Invoices
|
|||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Obsolete("Use GetCryptoData(network).TxFee instead")]
|
||||
public Money TxFee
|
||||
{
|
||||
get;
|
||||
|
@ -251,6 +208,10 @@ namespace BTCPayServer.Services.Invoices
|
|||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Obsolete("Use Set/GetCryptoData() instead")]
|
||||
public JObject CryptoData { get; set; }
|
||||
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public DateTimeOffset MonitoringExpiration
|
||||
{
|
||||
|
@ -275,7 +236,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
|
||||
|
||||
public InvoiceResponse EntityToDTO()
|
||||
public InvoiceResponse EntityToDTO(BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
ServerUrl = ServerUrl ?? "";
|
||||
InvoiceResponse dto = new InvoiceResponse
|
||||
|
@ -286,34 +247,62 @@ namespace BTCPayServer.Services.Invoices
|
|||
CurrentTime = DateTimeOffset.UtcNow,
|
||||
InvoiceTime = InvoiceTime,
|
||||
ExpirationTime = ExpirationTime,
|
||||
BTCPrice = Money.Coins((decimal)(ProductInformation.Price / Rate)).ToString(),
|
||||
Status = Status,
|
||||
Url = ServerUrl.WithTrailingSlash() + "invoice?id=" + Id,
|
||||
Currency = ProductInformation.Currency,
|
||||
Flags = new Flags() { Refundable = Refundable },
|
||||
Rate = Rate
|
||||
Flags = new Flags() { Refundable = Refundable }
|
||||
};
|
||||
|
||||
dto.CryptoInfo = new List<InvoiceCryptoInfo>();
|
||||
foreach (var info in this.GetCryptoData().Values)
|
||||
{
|
||||
var cryptoInfo = new InvoiceCryptoInfo();
|
||||
cryptoInfo.CryptoCode = info.CryptoCode;
|
||||
cryptoInfo.Rate = info.Rate;
|
||||
cryptoInfo.Price = Money.Coins(ProductInformation.Price / cryptoInfo.Rate).ToString();
|
||||
cryptoInfo.Due = info.GetCryptoDue().ToString();
|
||||
var paid = Payments.Where(p => p.Accounted && p.GetCryptoCode() == info.CryptoCode).Select(p => p.GetValue()).Sum();
|
||||
cryptoInfo.Paid = paid.ToString();
|
||||
cryptoInfo.Address = info.DepositAddress;
|
||||
cryptoInfo.ExRates = new Dictionary<string, double>
|
||||
{
|
||||
{ ProductInformation.Currency, (double)cryptoInfo.Rate }
|
||||
};
|
||||
|
||||
var scheme = networkProvider.GetNetwork(info.CryptoCode)?.UriScheme ?? "BTC";
|
||||
var cryptoSuffix = cryptoInfo.CryptoCode == "BTC" ? "" : "/" + cryptoInfo.CryptoCode;
|
||||
cryptoInfo.Url = ServerUrl.WithTrailingSlash() + $"invoice{cryptoSuffix}?id=" + Id;
|
||||
|
||||
|
||||
cryptoInfo.PaymentUrls = new 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}",
|
||||
};
|
||||
#pragma warning disable CS0618
|
||||
if (info.CryptoCode == "BTC")
|
||||
{
|
||||
dto.Url = cryptoInfo.Url;
|
||||
dto.BTCPrice = cryptoInfo.Price;
|
||||
dto.Rate = cryptoInfo.Rate;
|
||||
dto.ExRates = cryptoInfo.ExRates;
|
||||
dto.BitcoinAddress = cryptoInfo.Address;
|
||||
dto.BTCPaid = cryptoInfo.Paid;
|
||||
dto.BTCDue = cryptoInfo.Due;
|
||||
dto.PaymentUrls = cryptoInfo.PaymentUrls;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
dto.CryptoInfo.Add(cryptoInfo);
|
||||
}
|
||||
|
||||
Populate(ProductInformation, dto);
|
||||
Populate(BuyerInformation, dto);
|
||||
dto.ExRates = new Dictionary<string, double>
|
||||
{
|
||||
{ ProductInformation.Currency, Rate }
|
||||
};
|
||||
dto.PaymentUrls = new InvoicePaymentUrls()
|
||||
{
|
||||
BIP72 = $"bitcoin:{DepositAddress}?amount={GetCryptoDue()}&r={ServerUrl.WithTrailingSlash() + ($"i/{Id}")}",
|
||||
BIP72b = $"bitcoin:?r={ServerUrl.WithTrailingSlash() + ($"i/{Id}")}",
|
||||
BIP73 = ServerUrl.WithTrailingSlash() + ($"i/{Id}"),
|
||||
BIP21 = $"bitcoin:{DepositAddress}?amount={GetCryptoDue()}",
|
||||
};
|
||||
dto.BitcoinAddress = DepositAddress.ToString();
|
||||
|
||||
dto.Token = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); //No idea what it is useful for
|
||||
dto.Guid = Guid.NewGuid().ToString();
|
||||
|
||||
var paid = Payments.Where(p => p.Accounted).Select(p => p.Output.Value).Sum();
|
||||
dto.BTCPaid = paid.ToString();
|
||||
dto.BTCDue = GetCryptoDue().ToString();
|
||||
|
||||
dto.ExceptionStatus = ExceptionStatus == null ? new JValue(false) : new JValue(ExceptionStatus);
|
||||
return dto;
|
||||
}
|
||||
|
@ -324,11 +313,135 @@ namespace BTCPayServer.Services.Invoices
|
|||
JsonConvert.PopulateObject(str, dest);
|
||||
}
|
||||
|
||||
internal bool Support(BTCPayNetwork network)
|
||||
{
|
||||
var rates = GetCryptoData();
|
||||
return rates.TryGetValue(network.CryptoCode, out var data);
|
||||
}
|
||||
|
||||
public CryptoData GetCryptoData(string cryptoCode)
|
||||
{
|
||||
GetCryptoData().TryGetValue(cryptoCode, out var data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public CryptoData GetCryptoData(BTCPayNetwork network)
|
||||
{
|
||||
GetCryptoData().TryGetValue(network.CryptoCode, out var data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public Dictionary<string, CryptoData> GetCryptoData()
|
||||
{
|
||||
Dictionary<string, CryptoData> rates = new Dictionary<string, CryptoData>();
|
||||
var serializer = new Serializer(Dummy);
|
||||
#pragma warning disable CS0618
|
||||
// Legacy
|
||||
if (Rate != 0.0m)
|
||||
{
|
||||
rates.TryAdd("BTC", new CryptoData() { ParentEntity = this, Rate = Rate, CryptoCode = "BTC", TxFee = TxFee, FeeRate = new FeeRate(TxFee, 100), DepositAddress = DepositAddress });
|
||||
}
|
||||
if (CryptoData != null)
|
||||
{
|
||||
foreach (var prop in CryptoData.Properties())
|
||||
{
|
||||
var r = serializer.ToObject<CryptoData>(prop.Value.ToString());
|
||||
r.CryptoCode = prop.Name;
|
||||
r.ParentEntity = this;
|
||||
rates.TryAdd(r.CryptoCode, r);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
return rates;
|
||||
}
|
||||
|
||||
Network Dummy = Network.Main;
|
||||
public void SetCryptoData(Dictionary<string, CryptoData> cryptoData)
|
||||
{
|
||||
var obj = new JObject();
|
||||
var serializer = new Serializer(Dummy);
|
||||
foreach (var kv in cryptoData)
|
||||
{
|
||||
var clone = serializer.ToObject<CryptoData>(serializer.ToString(kv.Value));
|
||||
clone.CryptoCode = null;
|
||||
obj.Add(new JProperty(kv.Key, JObject.Parse(serializer.ToString(clone))));
|
||||
}
|
||||
#pragma warning disable CS0618
|
||||
CryptoData = obj;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
}
|
||||
|
||||
public class CryptoData
|
||||
{
|
||||
[JsonIgnore]
|
||||
public InvoiceEntity ParentEntity { get; set; }
|
||||
[JsonProperty(PropertyName = "cryptoCode", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string CryptoCode { get; set; }
|
||||
[JsonProperty(PropertyName = "rate")]
|
||||
public decimal Rate { get; set; }
|
||||
[JsonProperty(PropertyName = "feeRate")]
|
||||
public FeeRate FeeRate { get; set; }
|
||||
[JsonProperty(PropertyName = "txFee")]
|
||||
public Money TxFee { get; set; }
|
||||
[JsonProperty(PropertyName = "depositAddress")]
|
||||
public string DepositAddress { get; set; }
|
||||
|
||||
public Money GetNetworkFee()
|
||||
{
|
||||
var item = Calculate();
|
||||
return TxFee * item.TxCount;
|
||||
}
|
||||
|
||||
public int GetTxCount()
|
||||
{
|
||||
return Calculate().TxCount;
|
||||
}
|
||||
|
||||
public Money GetTotalCryptoDue()
|
||||
{
|
||||
return Calculate().TotalDue;
|
||||
}
|
||||
|
||||
private (Money TotalDue, Money Paid, int TxCount) Calculate()
|
||||
{
|
||||
var cryptoData = ParentEntity.GetCryptoData();
|
||||
var totalDue = Money.Coins(ParentEntity.ProductInformation.Price / Rate) + TxFee;
|
||||
var paid = Money.Zero;
|
||||
int txCount = 1;
|
||||
var payments =
|
||||
ParentEntity.Payments
|
||||
.Where(p => p.Accounted)
|
||||
.OrderByDescending(p => p.ReceivedTime)
|
||||
.Select(_ =>
|
||||
{
|
||||
paid += _.GetValue(cryptoData, CryptoCode);
|
||||
return _;
|
||||
})
|
||||
.TakeWhile(_ =>
|
||||
{
|
||||
var paidEnough = totalDue <= paid;
|
||||
if (!paidEnough && _.GetCryptoCode() == CryptoCode)
|
||||
{
|
||||
txCount++;
|
||||
totalDue += TxFee;
|
||||
}
|
||||
return !paidEnough;
|
||||
})
|
||||
.ToArray();
|
||||
return (totalDue, paid, txCount);
|
||||
}
|
||||
|
||||
public Money GetTotalPaid()
|
||||
{
|
||||
return Calculate().Paid;
|
||||
}
|
||||
public Money GetCryptoDue()
|
||||
{
|
||||
var o = Calculate();
|
||||
var v = o.TotalDue - o.Paid;
|
||||
return v < Money.Zero ? Money.Zero : v;
|
||||
}
|
||||
}
|
||||
|
||||
public class AccountedPaymentEntity
|
||||
|
@ -352,13 +465,59 @@ namespace BTCPayServer.Services.Invoices
|
|||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Obsolete("Use GetValue() or GetScriptPubKey() instead")]
|
||||
public TxOut Output
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Script GetScriptPubKey()
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
return Output.ScriptPubKey;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
public bool Accounted
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Obsolete("Use GetCryptoCode() instead")]
|
||||
public string CryptoCode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Money GetValue()
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
return Output.Value;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
public Money GetValue(Dictionary<string, CryptoData> cryptoData, string cryptoCode)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
var to = cryptoCode;
|
||||
var from = GetCryptoCode();
|
||||
if (to == from)
|
||||
return Output.Value;
|
||||
var fromRate = cryptoData[from].Rate;
|
||||
var toRate = cryptoData[to].Rate;
|
||||
|
||||
var fiatValue = fromRate * Output.Value.ToDecimal(MoneyUnit.BTC);
|
||||
var otherCurrencyValue = toRate == 0 ? 0.0m : fiatValue / toRate;
|
||||
return Money.Coins(otherCurrencyValue);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
public string GetCryptoCode()
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
return CryptoCode ?? "BTC";
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,17 +45,20 @@ namespace BTCPayServer.Services.Invoices
|
|||
IBackgroundJobClient _JobClient;
|
||||
EventAggregator _EventAggregator;
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
|
||||
public InvoiceNotificationManager(
|
||||
IBackgroundJobClient jobClient,
|
||||
EventAggregator eventAggregator,
|
||||
InvoiceRepository invoiceRepository,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
ILogger<InvoiceNotificationManager> logger)
|
||||
{
|
||||
Logger = logger as ILogger ?? NullLogger.Instance;
|
||||
_JobClient = jobClient;
|
||||
_EventAggregator = eventAggregator;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
_NetworkProvider = networkProvider;
|
||||
}
|
||||
|
||||
async Task Notify(InvoiceEntity invoice)
|
||||
|
@ -110,19 +113,15 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> SendNotification(InvoiceEntity invoice, CancellationToken cancellation)
|
||||
private async Task<HttpResponseMessage> SendNotification(InvoiceEntity invoice, CancellationToken cancellation)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Method = HttpMethod.Post;
|
||||
|
||||
var dto = invoice.EntityToDTO();
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
InvoicePaymentNotification notification = new InvoicePaymentNotification()
|
||||
{
|
||||
Id = dto.Id,
|
||||
Url = dto.Url,
|
||||
BTCDue = dto.BTCDue,
|
||||
BTCPaid = dto.BTCPaid,
|
||||
BTCPrice = dto.BTCPrice,
|
||||
Currency = dto.Currency,
|
||||
CurrentTime = dto.CurrentTime,
|
||||
ExceptionStatus = dto.ExceptionStatus,
|
||||
|
@ -130,10 +129,23 @@ namespace BTCPayServer.Services.Invoices
|
|||
InvoiceTime = dto.InvoiceTime,
|
||||
PosData = dto.PosData,
|
||||
Price = dto.Price,
|
||||
Rate = dto.Rate,
|
||||
Status = dto.Status,
|
||||
BuyerFields = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", invoice.RefundMail) }
|
||||
};
|
||||
|
||||
// We keep backward compatibility with bitpay by passing BTC info to the notification
|
||||
// we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
|
||||
var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "BTC");
|
||||
if(btcCryptoInfo != null)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
notification.Rate = (double)dto.Rate;
|
||||
notification.Url = dto.Url;
|
||||
notification.BTCDue = dto.BTCDue;
|
||||
notification.BTCPaid = dto.BTCPaid;
|
||||
notification.BTCPrice = dto.BTCPrice;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
request.RequestUri = new Uri(invoice.NotificationURL, UriKind.Absolute);
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(notification), Encoding.UTF8, "application/json");
|
||||
var response = await _Client.SendAsync(request, cancellation);
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
_Network = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ApplicationDbContextFactory _ContextFactory;
|
||||
private CustomThreadPool _IndexerThread;
|
||||
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath, Network network)
|
||||
|
@ -102,8 +102,9 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice)
|
||||
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
List<string> textSearch = new List<string>();
|
||||
invoice = Clone(invoice);
|
||||
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
||||
invoice.Payments = new List<PaymentEntity>();
|
||||
|
@ -121,52 +122,77 @@ namespace BTCPayServer.Services.Invoices
|
|||
ItemCode = invoice.ProductInformation.ItemCode,
|
||||
CustomerEmail = invoice.RefundMail
|
||||
});
|
||||
context.AddressInvoices.Add(new AddressInvoiceData()
|
||||
|
||||
foreach (var cryptoData in invoice.GetCryptoData().Values)
|
||||
{
|
||||
Address = invoice.DepositAddress.ScriptPubKey.Hash.ToString(),
|
||||
InvoiceDataId = invoice.Id,
|
||||
CreatedTime = DateTimeOffset.UtcNow,
|
||||
});
|
||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoice.Id,
|
||||
Address = invoice.DepositAddress.ToString(),
|
||||
Assigned = DateTimeOffset.UtcNow
|
||||
});
|
||||
var network = networkProvider.GetNetwork(cryptoData.CryptoCode);
|
||||
if (network == null)
|
||||
throw new InvalidOperationException("CryptoCode unsupported");
|
||||
context.AddressInvoices.Add(new AddressInvoiceData()
|
||||
{
|
||||
Address = BitcoinAddress.Create(cryptoData.DepositAddress, network.NBitcoinNetwork).ScriptPubKey.Hash.ToString(),
|
||||
InvoiceDataId = invoice.Id,
|
||||
CreatedTime = DateTimeOffset.UtcNow,
|
||||
});
|
||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoice.Id,
|
||||
Address = cryptoData.DepositAddress,
|
||||
#pragma warning disable CS0618
|
||||
CryptoCode = cryptoData.CryptoCode,
|
||||
#pragma warning restore CS0618
|
||||
Assigned = DateTimeOffset.UtcNow
|
||||
});
|
||||
textSearch.Add(cryptoData.DepositAddress);
|
||||
textSearch.Add(cryptoData.GetTotalCryptoDue().ToString());
|
||||
}
|
||||
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id });
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
AddToTextSearch(invoice.Id,
|
||||
invoice.Id,
|
||||
invoice.DepositAddress.ToString(),
|
||||
invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture),
|
||||
invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture),
|
||||
invoice.GetTotalCryptoDue().ToString(),
|
||||
invoice.OrderId,
|
||||
ToString(invoice.BuyerInformation),
|
||||
ToString(invoice.ProductInformation),
|
||||
invoice.StoreId
|
||||
);
|
||||
textSearch.Add(invoice.Id);
|
||||
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
||||
textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
|
||||
textSearch.Add(invoice.OrderId);
|
||||
textSearch.Add(ToString(invoice.BuyerInformation));
|
||||
textSearch.Add(ToString(invoice.ProductInformation));
|
||||
textSearch.Add(invoice.StoreId);
|
||||
|
||||
AddToTextSearch(invoice.Id, textSearch.ToArray());
|
||||
|
||||
return invoice;
|
||||
}
|
||||
|
||||
public async Task<bool> NewAddress(string invoiceId, BitcoinAddress bitcoinAddress)
|
||||
public async Task<bool> NewAddress(string invoiceId, BitcoinAddress bitcoinAddress, BTCPayNetwork network)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoice = await context.Invoices.FirstOrDefaultAsync(i => i.Id == invoiceId);
|
||||
if (invoice == null)
|
||||
return false;
|
||||
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob);
|
||||
var old = invoiceEntity.DepositAddress;
|
||||
invoiceEntity.DepositAddress = bitcoinAddress;
|
||||
invoice.Blob = ToBytes(invoiceEntity);
|
||||
if (old != null)
|
||||
var cryptoData = invoiceEntity.GetCryptoData();
|
||||
var currencyData = cryptoData.Where(c => c.Value.CryptoCode == network.CryptoCode).Select(f => f.Value).FirstOrDefault();
|
||||
if (currencyData == null)
|
||||
return false;
|
||||
|
||||
if (currencyData.DepositAddress != null)
|
||||
{
|
||||
MarkUnassigned(invoiceId, old, context);
|
||||
MarkUnassigned(invoiceId, invoiceEntity, context, network.CryptoCode);
|
||||
}
|
||||
|
||||
currencyData.DepositAddress = bitcoinAddress.ToString();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
if (network.CryptoCode == "BTC")
|
||||
{
|
||||
invoiceEntity.DepositAddress = currencyData.DepositAddress;
|
||||
}
|
||||
#pragma warning disable CS0618
|
||||
invoiceEntity.SetCryptoData(cryptoData);
|
||||
invoice.Blob = ToBytes(invoiceEntity);
|
||||
|
||||
context.AddressInvoices.Add(new AddressInvoiceData() { Address = bitcoinAddress.ScriptPubKey.Hash.ToString(), InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow });
|
||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||
{
|
||||
|
@ -181,14 +207,19 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
}
|
||||
|
||||
private static void MarkUnassigned(string invoiceId, BitcoinAddress old, ApplicationDbContext context)
|
||||
private static void MarkUnassigned(string invoiceId, InvoiceEntity entity, ApplicationDbContext context, string cryptoCode)
|
||||
{
|
||||
var historical = new HistoricalAddressInvoiceData();
|
||||
historical.InvoiceDataId = invoiceId;
|
||||
historical.Address = old.ToString();
|
||||
historical.UnAssigned = DateTimeOffset.UtcNow;
|
||||
context.Attach(historical);
|
||||
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
||||
foreach (var address in entity.GetCryptoData())
|
||||
{
|
||||
if (cryptoCode != null && cryptoCode != address.Value.CryptoCode)
|
||||
continue;
|
||||
var historical = new HistoricalAddressInvoiceData();
|
||||
historical.InvoiceDataId = invoiceId;
|
||||
historical.Address = address.Value.DepositAddress;
|
||||
historical.UnAssigned = DateTimeOffset.UtcNow;
|
||||
context.Attach(historical);
|
||||
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UnaffectAddress(string invoiceId)
|
||||
|
@ -199,9 +230,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
if (invoiceData == null)
|
||||
return;
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob);
|
||||
if (invoiceEntity.DepositAddress == null)
|
||||
return;
|
||||
MarkUnassigned(invoiceId, invoiceEntity.DepositAddress, context);
|
||||
MarkUnassigned(invoiceId, invoiceEntity, context, null);
|
||||
try
|
||||
{
|
||||
await context.SaveChangesAsync();
|
||||
|
@ -223,7 +252,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
|
||||
void AddToTextSearch(string invoiceId, params string[] terms)
|
||||
{
|
||||
_IndexerThread.DoAsync(() =>
|
||||
_IndexerThread.DoAsync(() =>
|
||||
{
|
||||
using (var tx = _Engine.GetTransaction())
|
||||
{
|
||||
|
@ -284,7 +313,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
private InvoiceEntity ToEntity(InvoiceData invoice)
|
||||
{
|
||||
var entity = ToObject<InvoiceEntity>(invoice.Blob);
|
||||
entity.Payments = invoice.Payments.Select(p =>
|
||||
entity.Payments = invoice.Payments.Select(p =>
|
||||
{
|
||||
var paymentEntity = ToObject<PaymentEntity>(p.Blob);
|
||||
paymentEntity.Accounted = p.Accounted;
|
||||
|
|
|
@ -29,9 +29,10 @@ namespace BTCPayServer.Services.Invoices
|
|||
DerivationStrategyFactory _DerivationFactory;
|
||||
EventAggregator _EventAggregator;
|
||||
BTCPayWallet _Wallet;
|
||||
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
|
||||
public InvoiceWatcher(ExplorerClient explorerClient,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayWallet wallet,
|
||||
|
@ -44,6 +45,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
_DerivationFactory = new DerivationStrategyFactory(_ExplorerClient.Network);
|
||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||
_EventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_NetworkProvider = networkProvider;
|
||||
accessor.Instance = this;
|
||||
}
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
|
@ -126,6 +128,7 @@ namespace BTCPayServer.Services.Invoices
|
|||
var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);
|
||||
changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false);
|
||||
|
||||
|
||||
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
|
||||
List<Coin> receivedCoins = new List<Coin>();
|
||||
foreach (var received in utxos)
|
||||
|
@ -142,7 +145,9 @@ namespace BTCPayServer.Services.Invoices
|
|||
dirtyAddress = true;
|
||||
}
|
||||
//////
|
||||
|
||||
var network = _NetworkProvider.GetNetwork("BTC");
|
||||
var cryptoData = invoice.GetCryptoData(network);
|
||||
var cryptoDataAll = invoice.GetCryptoData();
|
||||
if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
||||
{
|
||||
needSave = true;
|
||||
|
@ -154,8 +159,8 @@ namespace BTCPayServer.Services.Invoices
|
|||
|
||||
if (invoice.Status == "new" || invoice.Status == "expired")
|
||||
{
|
||||
var totalPaid = (await GetPaymentsWithTransaction(invoice)).Select(p => p.Payment.Output.Value).Sum();
|
||||
if (totalPaid >= invoice.GetTotalCryptoDue())
|
||||
var totalPaid = (await GetPaymentsWithTransaction(invoice)).Select(p => p.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
if (totalPaid >= cryptoData.GetTotalCryptoDue())
|
||||
{
|
||||
if (invoice.Status == "new")
|
||||
{
|
||||
|
@ -172,23 +177,23 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
}
|
||||
|
||||
if (totalPaid > invoice.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver")
|
||||
if (totalPaid > cryptoData.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver")
|
||||
{
|
||||
invoice.ExceptionStatus = "paidOver";
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
needSave = true;
|
||||
}
|
||||
|
||||
if (totalPaid < invoice.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||
if (totalPaid < cryptoData.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||
{
|
||||
Logs.PayServer.LogInformation("Paid to " + invoice.DepositAddress);
|
||||
Logs.PayServer.LogInformation("Paid to " + cryptoData.DepositAddress);
|
||||
invoice.ExceptionStatus = "paidPartial";
|
||||
needSave = true;
|
||||
if (dirtyAddress)
|
||||
{
|
||||
var address = await _Wallet.ReserveAddressAsync(_DerivationFactory.Parse(invoice.DerivationStrategy));
|
||||
Logs.PayServer.LogInformation("Generate new " + address);
|
||||
await _InvoiceRepository.NewAddress(invoice.Id, address);
|
||||
await _InvoiceRepository.NewAddress(invoice.Id, address, network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,13 +215,13 @@ namespace BTCPayServer.Services.Invoices
|
|||
transactions = transactions.Where(t => t.Confirmations >= 6);
|
||||
}
|
||||
|
||||
var chainTotalConfirmed = chainConfirmedTransactions.Select(t => t.Payment.Output.Value).Sum();
|
||||
var chainTotalConfirmed = chainConfirmedTransactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
|
||||
if (// Is after the monitoring deadline
|
||||
(invoice.MonitoringExpiration < DateTimeOffset.UtcNow)
|
||||
&&
|
||||
// And not enough amount confirmed
|
||||
(chainTotalConfirmed < invoice.GetTotalCryptoDue()))
|
||||
(chainTotalConfirmed < cryptoData.GetTotalCryptoDue()))
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "invalid")));
|
||||
|
@ -225,8 +230,8 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
else
|
||||
{
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
||||
if (totalConfirmed >= invoice.GetTotalCryptoDue())
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
if (totalConfirmed >= cryptoData.GetTotalCryptoDue())
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "confirmed")));
|
||||
|
@ -240,8 +245,8 @@ namespace BTCPayServer.Services.Invoices
|
|||
{
|
||||
var transactions = await GetPaymentsWithTransaction(invoice);
|
||||
transactions = transactions.Where(t => t.Confirmations >= 6);
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
||||
if (totalConfirmed >= invoice.GetTotalCryptoDue())
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
if (totalConfirmed >= cryptoData.GetTotalCryptoDue())
|
||||
{
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "complete")));
|
||||
invoice.Status = "complete";
|
||||
|
|
Loading…
Add table
Reference in a new issue