mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Big refactoring for supporting new type of payment
This commit is contained in:
parent
752133b01c
commit
aa4519ac30
@ -27,6 +27,7 @@ using System.Collections.Generic;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using System.Threading.Tasks;
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -50,13 +51,13 @@ namespace BTCPayServer.Tests
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
|
||||
// Some check that handling legacy stuff does not break things
|
||||
var cryptoData = entity.GetCryptoData("BTC", null, true);
|
||||
var cryptoData = entity.GetCryptoData(null, true).TryGet("BTC", PaymentTypes.BTCLike);
|
||||
cryptoData.Calculate();
|
||||
Assert.NotNull(cryptoData);
|
||||
Assert.Null(entity.GetCryptoData("BTC", null, false));
|
||||
Assert.Null(entity.GetCryptoData(null, false).TryGet("BTC", PaymentTypes.BTCLike));
|
||||
entity.SetCryptoData(new CryptoData() { ParentEntity = entity, Rate = entity.Rate, CryptoCode = "BTC", TxFee = entity.TxFee });
|
||||
Assert.NotNull(entity.GetCryptoData("BTC", null, false));
|
||||
Assert.NotNull(entity.GetCryptoData("BTC", null, true));
|
||||
Assert.NotNull(entity.GetCryptoData(null, false).TryGet("BTC", PaymentTypes.BTCLike));
|
||||
Assert.NotNull(entity.GetCryptoData(null, true).TryGet("BTC", PaymentTypes.BTCLike));
|
||||
////////////////////
|
||||
|
||||
var accounting = cryptoData.Calculate();
|
||||
@ -90,30 +91,32 @@ namespace BTCPayServer.Tests
|
||||
|
||||
entity = new InvoiceEntity();
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
entity.SetCryptoData(new System.Collections.Generic.Dictionary<string, CryptoData>(new KeyValuePair<string, CryptoData>[] {
|
||||
new KeyValuePair<string,CryptoData>("BTC", new CryptoData()
|
||||
{
|
||||
Rate = 1000,
|
||||
TxFee = Money.Coins(0.1m)
|
||||
}),
|
||||
new KeyValuePair<string,CryptoData>("LTC", new CryptoData()
|
||||
{
|
||||
Rate = 500,
|
||||
TxFee = Money.Coins(0.01m)
|
||||
})
|
||||
}));
|
||||
CryptoDataDictionary cryptoDatas = new CryptoDataDictionary();
|
||||
cryptoDatas.Add(new CryptoData()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
Rate = 1000,
|
||||
TxFee = Money.Coins(0.1m)
|
||||
});
|
||||
cryptoDatas.Add(new CryptoData()
|
||||
{
|
||||
CryptoCode = "LTC",
|
||||
Rate = 500,
|
||||
TxFee = Money.Coins(0.01m)
|
||||
});
|
||||
entity.SetCryptoData(cryptoDatas);
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
cryptoData = entity.GetCryptoData("BTC", null);
|
||||
cryptoData = entity.GetCryptoData(new CryptoDataId("BTC", PaymentTypes.BTCLike), null);
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(5.1m), accounting.Due);
|
||||
|
||||
cryptoData = entity.GetCryptoData("LTC", null);
|
||||
cryptoData = entity.GetCryptoData(new CryptoDataId("LTC", PaymentTypes.BTCLike), null);
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true });
|
||||
|
||||
cryptoData = entity.GetCryptoData("BTC", null);
|
||||
cryptoData = entity.GetCryptoData(new CryptoDataId("BTC", PaymentTypes.BTCLike), null);
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(4.2m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
@ -121,7 +124,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(Money.Coins(5.2m), accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxCount);
|
||||
|
||||
cryptoData = entity.GetCryptoData("LTC", null);
|
||||
cryptoData = entity.GetCryptoData(new CryptoDataId("LTC", PaymentTypes.BTCLike), null);
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 - 2.0m /* 8.21m */), accounting.Due);
|
||||
Assert.Equal(Money.Coins(0.0m), accounting.CryptoPaid);
|
||||
@ -131,7 +134,7 @@ namespace BTCPayServer.Tests
|
||||
entity.Payments.Add(new PaymentEntity() { CryptoCode = "LTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true });
|
||||
|
||||
|
||||
cryptoData = entity.GetCryptoData("BTC", null);
|
||||
cryptoData = entity.GetCryptoData(new CryptoDataId("BTC", PaymentTypes.BTCLike), null);
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(4.2m - 0.5m + 0.01m / 2), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
@ -139,7 +142,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue); // The fee for LTC added
|
||||
Assert.Equal(2, accounting.TxCount);
|
||||
|
||||
cryptoData = entity.GetCryptoData("LTC", null);
|
||||
cryptoData = entity.GetCryptoData(new CryptoDataId("LTC", PaymentTypes.BTCLike), null);
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(8.21m - 1.0m + 0.01m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
@ -150,7 +153,7 @@ namespace BTCPayServer.Tests
|
||||
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2);
|
||||
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(remaining, new Key()), Accounted = true });
|
||||
|
||||
cryptoData = entity.GetCryptoData("BTC", null);
|
||||
cryptoData = entity.GetCryptoData(new CryptoDataId("BTC", PaymentTypes.BTCLike), null);
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m) + remaining, accounting.CryptoPaid);
|
||||
@ -159,7 +162,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxCount);
|
||||
|
||||
cryptoData = entity.GetCryptoData("LTC", null);
|
||||
cryptoData = entity.GetCryptoData(new CryptoDataId("LTC", PaymentTypes.BTCLike), null);
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
||||
|
@ -26,7 +26,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.0" />
|
||||
<PackageReference Include="NBitcoin" Version="4.0.0.55" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.16" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.17" />
|
||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.1.9" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
|
||||
|
@ -9,13 +9,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}")]
|
||||
[Route("i/{invoiceId}/{cryptoCode?}")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")]
|
||||
public async Task<IActionResult> GetInvoiceRequest(string invoiceId, string cryptoCode = null)
|
||||
{
|
||||
@ -23,11 +24,12 @@ namespace BTCPayServer.Controllers
|
||||
cryptoCode = "BTC";
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (invoice == null || invoice.IsExpired() || network == null || !invoice.Support(network))
|
||||
var cryptoId = new CryptoDataId(cryptoCode, Payments.PaymentTypes.BTCLike);
|
||||
if (invoice == null || invoice.IsExpired() || network == null || !invoice.Support(cryptoId))
|
||||
return NotFound();
|
||||
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var cryptoData = dto.CryptoInfo.First(c => c.CryptoCode.Equals(cryptoCode, StringComparison.OrdinalIgnoreCase));
|
||||
var cryptoData = dto.CryptoInfo.First(c => c.GetCryptoDataId() == cryptoId);
|
||||
PaymentRequest request = new PaymentRequest
|
||||
{
|
||||
DetailsVersion = 1
|
||||
@ -69,7 +71,7 @@ namespace BTCPayServer.Controllers
|
||||
if (cryptoCode == null)
|
||||
cryptoCode = "BTC";
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
if (network == null || invoice == null || invoice.IsExpired() || !invoice.Support(network))
|
||||
if (network == null || invoice == null || invoice.IsExpired() || !invoice.Support(new Services.Invoices.CryptoDataId(cryptoCode, Payments.PaymentTypes.BTCLike)))
|
||||
return NotFound();
|
||||
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
|
@ -20,6 +20,7 @@ using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Events;
|
||||
using NBXplorer;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -65,22 +66,27 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
foreach (var data in invoice.GetCryptoData(null))
|
||||
{
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.CryptoCode.Equals(data.Key, StringComparison.OrdinalIgnoreCase));
|
||||
var accounting = data.Value.Calculate();
|
||||
var paymentNetwork = _NetworkProvider.GetNetwork(data.Key);
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.GetCryptoDataId() == data.GetId());
|
||||
var accounting = data.Calculate();
|
||||
var paymentNetwork = _NetworkProvider.GetNetwork(data.GetId().CryptoCode);
|
||||
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
|
||||
cryptoPayment.CryptoCode = paymentNetwork.CryptoCode;
|
||||
cryptoPayment.Due = accounting.Due.ToString() + $" {paymentNetwork.CryptoCode}";
|
||||
cryptoPayment.Paid = accounting.CryptoPaid.ToString() + $" {paymentNetwork.CryptoCode}";
|
||||
cryptoPayment.Address = data.Value.DepositAddress;
|
||||
cryptoPayment.Rate = FormatCurrency(data.Value);
|
||||
|
||||
var onchainMethod = data.GetPaymentMethod() as BitcoinLikeOnChainPaymentMethod;
|
||||
if(onchainMethod != null)
|
||||
{
|
||||
cryptoPayment.Address = onchainMethod.DepositAddress.ToString();
|
||||
}
|
||||
cryptoPayment.Rate = FormatCurrency(data);
|
||||
cryptoPayment.PaymentUrl = cryptoInfo.PaymentUrls.BIP21;
|
||||
model.CryptoPayments.Add(cryptoPayment);
|
||||
}
|
||||
|
||||
var payments = invoice
|
||||
.GetPayments()
|
||||
.Where(p => p.GetCryptoPaymentDataType() == BitcoinLikePaymentData.OnchainBitcoinType)
|
||||
.Where(p => p.GetCryptoDataId().PaymentType == PaymentTypes.BTCLike)
|
||||
.Select(async payment =>
|
||||
{
|
||||
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
|
||||
@ -123,60 +129,65 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}")]
|
||||
[Route("i/{invoiceId}/{cryptoCode}")]
|
||||
[Route("i/{invoiceId}/{cryptoDataId}")]
|
||||
[Route("invoice")]
|
||||
[AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)]
|
||||
[XFrameOptionsAttribute(null)]
|
||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null, string cryptoCode = null)
|
||||
public async Task<IActionResult> Checkout(string invoiceId, string id = null, string cryptoDataId = null)
|
||||
{
|
||||
//Keep compatibility with Bitpay
|
||||
invoiceId = invoiceId ?? id;
|
||||
id = invoiceId;
|
||||
////
|
||||
|
||||
var model = await GetInvoiceModel(invoiceId, cryptoCode);
|
||||
var model = await GetInvoiceModel(invoiceId, cryptoDataId);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
return View(nameof(Checkout), model);
|
||||
}
|
||||
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, string cryptoCode)
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId, string cryptoDataId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
bool isDefaultCrypto = false;
|
||||
if (cryptoCode == null)
|
||||
{
|
||||
cryptoCode = store.GetDefaultCrypto();
|
||||
if (cryptoDataId == null)
|
||||
{
|
||||
cryptoDataId = store.GetDefaultCrypto();
|
||||
isDefaultCrypto = true;
|
||||
}
|
||||
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||
|
||||
var cryptoId = CryptoDataId.Parse(cryptoDataId);
|
||||
var network = _NetworkProvider.GetNetwork(cryptoId.CryptoCode);
|
||||
if (invoice == null || network == null)
|
||||
return null;
|
||||
|
||||
if(!invoice.Support(network))
|
||||
if (!invoice.Support(cryptoId))
|
||||
{
|
||||
if(!isDefaultCrypto)
|
||||
return null;
|
||||
network = invoice.GetCryptoData(_NetworkProvider).First().Value.Network;
|
||||
var firstCryptoData = invoice.GetCryptoData(_NetworkProvider).First();
|
||||
network = firstCryptoData.Network;
|
||||
cryptoId = firstCryptoData.GetId();
|
||||
}
|
||||
var cryptoData = invoice.GetCryptoData(network, _NetworkProvider);
|
||||
|
||||
var cryptoData = invoice.GetCryptoData(cryptoId, _NetworkProvider);
|
||||
var paymentMethod = cryptoData.GetPaymentMethod();
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.CryptoCode == network.CryptoCode);
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.GetCryptoDataId() == cryptoId);
|
||||
|
||||
var currency = invoice.ProductInformation.Currency;
|
||||
var accounting = cryptoData.Calculate();
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
CryptoCode = network.CryptoCode,
|
||||
CryptoDataId = cryptoId.ToString(),
|
||||
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
||||
OrderId = invoice.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
BtcAddress = cryptoData.DepositAddress,
|
||||
BtcAddress = paymentMethod.GetPaymentDestination(),
|
||||
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ToString(),
|
||||
BtcDue = accounting.Due.ToString(),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
@ -192,14 +203,14 @@ namespace BTCPayServer.Controllers
|
||||
BtcPaid = accounting.Paid.ToString(),
|
||||
Status = invoice.Status,
|
||||
CryptoImage = "/" + Url.Content(network.CryptoImagePath),
|
||||
NetworkFeeDescription = $"{accounting.TxCount} transaction{(accounting.TxCount > 1 ? "s" : "")} x {cryptoData.TxFee} {network.CryptoCode}",
|
||||
NetworkFeeDescription = $"{accounting.TxCount} transaction{(accounting.TxCount > 1 ? "s" : "")} x {paymentMethod.GetTxFee()} {network.CryptoCode}",
|
||||
AvailableCryptos = invoice.GetCryptoData(_NetworkProvider)
|
||||
.Where(i => i.Value.Network != null)
|
||||
.Where(i => i.Network != null)
|
||||
.Select(kv=> new PaymentModel.AvailableCrypto()
|
||||
{
|
||||
CryptoCode = kv.Key,
|
||||
CryptoImage = "/" + kv.Value.Network.CryptoImagePath,
|
||||
Link = Url.Action(nameof(Checkout), new { invoiceId = invoiceId, cryptoCode = kv.Key })
|
||||
CryptoDataId = kv.GetId().ToString(),
|
||||
CryptoImage = "/" + kv.Network.CryptoImagePath,
|
||||
Link = Url.Action(nameof(Checkout), new { invoiceId = invoiceId, cryptoDataId = kv.GetId().ToString() })
|
||||
}).Where(c => c.CryptoImage != "/")
|
||||
.ToList()
|
||||
};
|
||||
@ -236,10 +247,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/status")]
|
||||
[Route("i/{invoiceId}/{cryptoCode}/status")]
|
||||
public async Task<IActionResult> GetStatus(string invoiceId, string cryptoCode)
|
||||
[Route("i/{invoiceId}/{cryptoDataId}/status")]
|
||||
public async Task<IActionResult> GetStatus(string invoiceId, string cryptoDataId = null)
|
||||
{
|
||||
var model = await GetInvoiceModel(invoiceId, cryptoCode);
|
||||
var model = await GetInvoiceModel(invoiceId, cryptoDataId);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
return Json(model);
|
||||
|
@ -39,6 +39,7 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -136,16 +137,17 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
|
||||
bool legacyBTCisSet = false;
|
||||
var cryptoDatas = new Dictionary<string, CryptoData>();
|
||||
var cryptoDatas = new CryptoDataDictionary();
|
||||
foreach (var q in queries)
|
||||
{
|
||||
CryptoData cryptoData = new CryptoData();
|
||||
cryptoData.CryptoCode = q.network.CryptoCode;
|
||||
cryptoData.FeeRate = (await q.getFeeRate);
|
||||
cryptoData.TxFee = GetTxFee(storeBlob, cryptoData.FeeRate); // assume price for 100 bytes
|
||||
cryptoData.SetId(new CryptoDataId(q.network.CryptoCode, PaymentTypes.BTCLike));
|
||||
BitcoinLikeOnChainPaymentMethod onchainMethod = new BitcoinLikeOnChainPaymentMethod();
|
||||
onchainMethod.FeeRate = (await q.getFeeRate);
|
||||
onchainMethod.TxFee = GetTxFee(storeBlob, onchainMethod.FeeRate); // assume price for 100 bytes
|
||||
cryptoData.Rate = await q.getRate;
|
||||
cryptoData.DepositAddress = (await q.getAddress).ToString();
|
||||
|
||||
onchainMethod.DepositAddress = (await q.getAddress);
|
||||
cryptoData.SetPaymentMethod(onchainMethod);
|
||||
#pragma warning disable CS0618
|
||||
if (q.network.IsBTC)
|
||||
{
|
||||
@ -155,7 +157,7 @@ namespace BTCPayServer.Controllers
|
||||
entity.DepositAddress = cryptoData.DepositAddress;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
cryptoDatas.Add(cryptoData.CryptoCode, cryptoData);
|
||||
cryptoDatas.Add(cryptoData);
|
||||
}
|
||||
|
||||
if (!legacyBTCisSet)
|
||||
|
@ -22,11 +22,18 @@ using System.IO;
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Net.WebSockets;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitpayClient;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static CryptoDataId GetCryptoDataId(this InvoiceCryptoInfo info)
|
||||
{
|
||||
return new CryptoDataId(info.CryptoCode, Enum.Parse<PaymentTypes>(info.PaymentType));
|
||||
}
|
||||
public static async Task CloseSocket(this WebSocket webSocket)
|
||||
{
|
||||
try
|
||||
|
@ -201,7 +201,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
// 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");
|
||||
var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetCryptoDataId() == new CryptoDataId("BTC", Payments.PaymentTypes.BTCLike));
|
||||
if (btcCryptoInfo != null)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
|
@ -72,17 +72,16 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
var derivationStrategies = invoice.GetDerivationStrategies(_NetworkProvider).ToArray();
|
||||
var payments = invoice.GetPayments().Where(p => p.Accounted).ToArray();
|
||||
foreach (BTCPayNetwork network in _NetworkProvider.GetAll())
|
||||
var cryptoDataAll = invoice.GetCryptoData(_NetworkProvider);
|
||||
foreach (var cryptoData in cryptoDataAll.Select(c => c))
|
||||
{
|
||||
var cryptoData = invoice.GetCryptoData(network, _NetworkProvider);
|
||||
if (cryptoData == null) // Altcoin not supported
|
||||
continue;
|
||||
var cryptoDataAll = invoice.GetCryptoData(_NetworkProvider);
|
||||
var accounting = cryptoData.Calculate();
|
||||
|
||||
var network = _NetworkProvider.GetNetwork(cryptoData.GetId().CryptoCode);
|
||||
if (network == null)
|
||||
continue;
|
||||
if (invoice.Status == "new" || invoice.Status == "expired")
|
||||
{
|
||||
var totalPaid = payments.Select(p => p.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
var totalPaid = payments.Select(p => p.GetValue(cryptoDataAll, cryptoData.GetId())).Sum();
|
||||
if (totalPaid >= accounting.TotalDue)
|
||||
{
|
||||
if (invoice.Status == "new")
|
||||
@ -112,7 +111,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
var transactions = payments.Where(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network));
|
||||
|
||||
var totalConfirmed = transactions.Select(t => t.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
var totalConfirmed = transactions.Select(t => t.GetValue(cryptoDataAll, cryptoData.GetId())).Sum();
|
||||
|
||||
if (// Is after the monitoring deadline
|
||||
(invoice.MonitoringExpiration < DateTimeOffset.UtcNow)
|
||||
@ -137,7 +136,7 @@ namespace BTCPayServer.HostedServices
|
||||
if (invoice.Status == "confirmed")
|
||||
{
|
||||
var transactions = payments.Where(p => p.GetCryptoPaymentData().PaymentCompleted(p, network));
|
||||
var totalConfirmed = transactions.Select(t => t.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
var totalConfirmed = transactions.Select(t => t.GetValue(cryptoDataAll, cryptoData.GetId())).Sum();
|
||||
if (totalConfirmed >= accounting.TotalDue)
|
||||
{
|
||||
context.Events.Add(new InvoiceEvent(invoice, 1006, "invoice_completed"));
|
||||
|
@ -16,6 +16,7 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using NBitcoin;
|
||||
using NBXplorer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
@ -209,7 +210,7 @@ namespace BTCPayServer.HostedServices
|
||||
IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(InvoiceEntity invoice)
|
||||
{
|
||||
return invoice.GetPayments()
|
||||
.Where(p => p.GetCryptoPaymentDataType() == BitcoinLikePaymentData.OnchainBitcoinType)
|
||||
.Where(p => p.GetCryptoDataId().PaymentType == PaymentTypes.BTCLike)
|
||||
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData());
|
||||
}
|
||||
|
||||
@ -223,7 +224,7 @@ namespace BTCPayServer.HostedServices
|
||||
var conflicts = GetConflicts(transactions.Select(t => t.Value));
|
||||
foreach (var payment in invoice.GetPayments(wallet.Network))
|
||||
{
|
||||
if (payment.GetCryptoPaymentDataType() != BitcoinLikePaymentData.OnchainBitcoinType)
|
||||
if (payment.GetCryptoDataId().PaymentType != PaymentTypes.BTCLike)
|
||||
continue;
|
||||
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
|
||||
if (!transactions.TryGetValue(paymentData.Outpoint.Hash, out TransactionResult tx))
|
||||
@ -348,13 +349,15 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
|
||||
var invoice = (await UpdatePaymentStates(wallet, invoiceId));
|
||||
var cryptoData = invoice.GetCryptoData(wallet.Network, _ExplorerClients.NetworkProviders);
|
||||
if (cryptoData.GetDepositAddress().ScriptPubKey == paymentData.Output.ScriptPubKey && cryptoData.Calculate().Due > Money.Zero)
|
||||
var cryptoData = invoice.GetCryptoData(wallet.Network, PaymentTypes.BTCLike, _ExplorerClients.NetworkProviders);
|
||||
var method = cryptoData.GetPaymentMethod() as Payments.BitcoinLikeOnChainPaymentMethod;
|
||||
if (method.DepositAddress.ScriptPubKey == paymentData.Output.ScriptPubKey && cryptoData.Calculate().Due > Money.Zero)
|
||||
{
|
||||
var address = await wallet.ReserveAddressAsync(strategy);
|
||||
await _InvoiceRepository.NewAddress(invoiceId, address, wallet.Network);
|
||||
method.DepositAddress = address;
|
||||
await _InvoiceRepository.NewAddress(invoiceId, method, wallet.Network);
|
||||
_Aggregator.Publish(new InvoiceNewAddressEvent(invoiceId, address.ToString(), wallet.Network));
|
||||
cryptoData.DepositAddress = address.ToString();
|
||||
cryptoData.SetPaymentMethod(method);
|
||||
invoice.SetCryptoData(cryptoData);
|
||||
}
|
||||
wallet.InvalidateCache(strategy);
|
||||
|
@ -9,7 +9,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
public class AvailableCrypto
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string CryptoDataId { get; set; }
|
||||
public string CryptoImage { get; set; }
|
||||
public string Link { get; set; }
|
||||
}
|
||||
@ -40,5 +40,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string CryptoImage { get; set; }
|
||||
public string NetworkFeeDescription { get; internal set; }
|
||||
public int MaxTimeMinutes { get; internal set; }
|
||||
public string PaymentType { get; internal set; }
|
||||
public string CryptoDataId { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
110
BTCPayServer/Payments/BitcoinPayment.cs
Normal file
110
BTCPayServer/Payments/BitcoinPayment.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
public class BitcoinLikeOnChainPaymentMethod : IPaymentMethod
|
||||
{
|
||||
public PaymentTypes GetPaymentType()
|
||||
{
|
||||
return PaymentTypes.BTCLike;
|
||||
}
|
||||
|
||||
public string GetPaymentDestination()
|
||||
{
|
||||
return DepositAddress?.ToString();
|
||||
}
|
||||
|
||||
public Money GetTxFee()
|
||||
{
|
||||
return TxFee;
|
||||
}
|
||||
|
||||
public void SetPaymentDestination(string newPaymentDestination)
|
||||
{
|
||||
if (newPaymentDestination == null)
|
||||
DepositAddress = null;
|
||||
else
|
||||
DepositAddress = BitcoinAddress.Create(newPaymentDestination, DepositAddress.Network);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public FeeRate FeeRate { get; set; }
|
||||
[JsonIgnore]
|
||||
public Money TxFee { get; set; }
|
||||
[JsonIgnore]
|
||||
public BitcoinAddress DepositAddress { get; set; }
|
||||
}
|
||||
|
||||
public class BitcoinLikePaymentData : CryptoPaymentData
|
||||
{
|
||||
public PaymentTypes GetPaymentType()
|
||||
{
|
||||
return PaymentTypes.BTCLike;
|
||||
}
|
||||
public BitcoinLikePaymentData()
|
||||
{
|
||||
|
||||
}
|
||||
public BitcoinLikePaymentData(Coin coin, bool rbf)
|
||||
{
|
||||
Outpoint = coin.Outpoint;
|
||||
Output = coin.TxOut;
|
||||
ConfirmationCount = 0;
|
||||
RBF = rbf;
|
||||
}
|
||||
[JsonIgnore]
|
||||
public OutPoint Outpoint { get; set; }
|
||||
[JsonIgnore]
|
||||
public TxOut Output { get; set; }
|
||||
public int ConfirmationCount { get; set; }
|
||||
public bool RBF { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is set to true if the payment was created before CryptoPaymentData existed in BTCPayServer
|
||||
/// </summary>
|
||||
public bool Legacy { get; set; }
|
||||
|
||||
public string GetPaymentId()
|
||||
{
|
||||
return Outpoint.ToString();
|
||||
}
|
||||
|
||||
public string[] GetSearchTerms()
|
||||
{
|
||||
return new[] { Outpoint.Hash.ToString() };
|
||||
}
|
||||
|
||||
public Money GetValue()
|
||||
{
|
||||
return Output.Value;
|
||||
}
|
||||
|
||||
public bool PaymentCompleted(PaymentEntity entity, BTCPayNetwork network)
|
||||
{
|
||||
return ConfirmationCount >= network.MaxTrackedConfirmation;
|
||||
}
|
||||
|
||||
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network)
|
||||
{
|
||||
if (speedPolicy == SpeedPolicy.HighSpeed)
|
||||
{
|
||||
return ConfirmationCount >= 1 || !RBF;
|
||||
}
|
||||
else if (speedPolicy == SpeedPolicy.MediumSpeed)
|
||||
{
|
||||
return ConfirmationCount >= 1;
|
||||
}
|
||||
else if (speedPolicy == SpeedPolicy.LowSpeed)
|
||||
{
|
||||
return ConfirmationCount >= 6;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
12
BTCPayServer/Payments/PaymentTypes.cs
Normal file
12
BTCPayServer/Payments/PaymentTypes.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
public enum PaymentTypes
|
||||
{
|
||||
BTCLike
|
||||
}
|
||||
}
|
74
BTCPayServer/Services/Invoices/CryptoDataDictionary.cs
Normal file
74
BTCPayServer/Services/Invoices/CryptoDataDictionary.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
public class CryptoDataDictionary : IEnumerable<CryptoData>
|
||||
{
|
||||
Dictionary<CryptoDataId, CryptoData> _Inner = new Dictionary<CryptoDataId, CryptoData>();
|
||||
public CryptoDataDictionary()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public CryptoData this[CryptoDataId index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Inner[index];
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(CryptoData cryptoData)
|
||||
{
|
||||
_Inner.Add(cryptoData.GetId(), cryptoData);
|
||||
}
|
||||
|
||||
public void Remove(CryptoData cryptoData)
|
||||
{
|
||||
_Inner.Remove(cryptoData.GetId());
|
||||
}
|
||||
public bool TryGetValue(CryptoDataId cryptoDataId, out CryptoData data)
|
||||
{
|
||||
if (cryptoDataId == null)
|
||||
throw new ArgumentNullException(nameof(cryptoDataId));
|
||||
return _Inner.TryGetValue(cryptoDataId, out data);
|
||||
}
|
||||
|
||||
public void AddOrReplace(CryptoData cryptoData)
|
||||
{
|
||||
var key = cryptoData.GetId();
|
||||
_Inner.Remove(key);
|
||||
_Inner.Add(key, cryptoData);
|
||||
}
|
||||
|
||||
public IEnumerator<CryptoData> GetEnumerator()
|
||||
{
|
||||
return _Inner.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public CryptoData TryGet(CryptoDataId cryptoDataId)
|
||||
{
|
||||
if (cryptoDataId == null)
|
||||
throw new ArgumentNullException(nameof(cryptoDataId));
|
||||
_Inner.TryGetValue(cryptoDataId, out var value);
|
||||
return value;
|
||||
}
|
||||
public CryptoData TryGet(string network, PaymentTypes paymentType)
|
||||
{
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
var id = new CryptoDataId(network, paymentType);
|
||||
return TryGet(id);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using BTCPayServer.Data;
|
||||
using NBXplorer.Models;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
@ -347,11 +348,12 @@ namespace BTCPayServer.Services.Invoices
|
||||
};
|
||||
|
||||
dto.CryptoInfo = new List<NBitpayClient.InvoiceCryptoInfo>();
|
||||
foreach (var info in this.GetCryptoData(networkProvider, true).Values)
|
||||
foreach (var info in this.GetCryptoData(networkProvider, true))
|
||||
{
|
||||
var accounting = info.Calculate();
|
||||
var cryptoInfo = new NBitpayClient.InvoiceCryptoInfo();
|
||||
cryptoInfo.CryptoCode = info.CryptoCode;
|
||||
cryptoInfo.CryptoCode = info.GetId().CryptoCode;
|
||||
cryptoInfo.PaymentType = info.GetId().PaymentType.ToString();
|
||||
cryptoInfo.Rate = info.Rate;
|
||||
cryptoInfo.Price = Money.Coins(ProductInformation.Price / cryptoInfo.Rate).ToString();
|
||||
|
||||
@ -362,7 +364,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
cryptoInfo.TxCount = accounting.TxCount;
|
||||
cryptoInfo.CryptoPaid = accounting.CryptoPaid.ToString();
|
||||
|
||||
cryptoInfo.Address = info.DepositAddress;
|
||||
if (info.GetPaymentMethod() is BitcoinLikeOnChainPaymentMethod onchainMethod)
|
||||
cryptoInfo.Address = onchainMethod.DepositAddress?.ToString();
|
||||
cryptoInfo.ExRates = new Dictionary<string, double>
|
||||
{
|
||||
{ ProductInformation.Currency, (double)cryptoInfo.Rate }
|
||||
@ -413,27 +416,25 @@ namespace BTCPayServer.Services.Invoices
|
||||
JsonConvert.PopulateObject(str, dest);
|
||||
}
|
||||
|
||||
internal bool Support(BTCPayNetwork network)
|
||||
internal bool Support(CryptoDataId cryptoDataId)
|
||||
{
|
||||
var rates = GetCryptoData(null);
|
||||
return rates.TryGetValue(network.CryptoCode, out var data);
|
||||
return rates.TryGet(cryptoDataId) != null;
|
||||
}
|
||||
|
||||
public CryptoData GetCryptoData(string cryptoCode, BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
|
||||
public CryptoData GetCryptoData(CryptoDataId cryptoDataId, BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
GetCryptoData(networkProvider, alwaysIncludeBTC).TryGetValue(cryptoCode, out var data);
|
||||
GetCryptoData(networkProvider).TryGetValue(cryptoDataId, out var data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public CryptoData GetCryptoData(BTCPayNetwork network, BTCPayNetworkProvider networkProvider)
|
||||
public CryptoData GetCryptoData(BTCPayNetwork network, PaymentTypes paymentType, BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
GetCryptoData(networkProvider).TryGetValue(network.CryptoCode, out var data);
|
||||
return data;
|
||||
return GetCryptoData(new CryptoDataId(network.CryptoCode, paymentType), networkProvider);
|
||||
}
|
||||
|
||||
public Dictionary<string, CryptoData> GetCryptoData(BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
|
||||
public CryptoDataDictionary GetCryptoData(BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
|
||||
{
|
||||
Dictionary<string, CryptoData> rates = new Dictionary<string, CryptoData>();
|
||||
CryptoDataDictionary rates = new CryptoDataDictionary();
|
||||
var serializer = new Serializer(Dummy);
|
||||
CryptoData phantom = null;
|
||||
#pragma warning disable CS0618
|
||||
@ -442,19 +443,21 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
var btcNetwork = networkProvider?.GetNetwork("BTC");
|
||||
phantom = new CryptoData() { ParentEntity = this, IsPhantomBTC = true, Rate = Rate, CryptoCode = "BTC", TxFee = TxFee, FeeRate = new FeeRate(TxFee, 100), DepositAddress = DepositAddress, Network = btcNetwork };
|
||||
rates.Add("BTC", phantom);
|
||||
rates.Add(phantom);
|
||||
}
|
||||
if (CryptoData != null)
|
||||
{
|
||||
foreach (var prop in CryptoData.Properties())
|
||||
{
|
||||
if (prop.Name == "BTC" && phantom != null)
|
||||
rates.Remove("BTC");
|
||||
rates.Remove(phantom);
|
||||
var r = serializer.ToObject<CryptoData>(prop.Value.ToString());
|
||||
r.CryptoCode = prop.Name;
|
||||
var cryptoDataId = CryptoDataId.Parse(prop.Name);
|
||||
r.CryptoCode = cryptoDataId.CryptoCode;
|
||||
r.PaymentType = cryptoDataId.PaymentType.ToString();
|
||||
r.ParentEntity = this;
|
||||
r.Network = networkProvider?.GetNetwork(r.CryptoCode);
|
||||
rates.Add(r.CryptoCode, r);
|
||||
rates.Add(r);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@ -466,21 +469,22 @@ namespace BTCPayServer.Services.Invoices
|
||||
public void SetCryptoData(CryptoData cryptoData)
|
||||
{
|
||||
var dict = GetCryptoData(null);
|
||||
dict.AddOrReplace(cryptoData.CryptoCode, cryptoData);
|
||||
dict.AddOrReplace(cryptoData);
|
||||
SetCryptoData(dict);
|
||||
}
|
||||
|
||||
public void SetCryptoData(Dictionary<string, CryptoData> cryptoData)
|
||||
public void SetCryptoData(CryptoDataDictionary 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
|
||||
foreach (var v in cryptoData)
|
||||
{
|
||||
var clone = serializer.ToObject<CryptoData>(serializer.ToString(v));
|
||||
clone.CryptoCode = null;
|
||||
clone.PaymentType = null;
|
||||
obj.Add(new JProperty(v.GetId().ToString(), JObject.Parse(serializer.ToString(clone))));
|
||||
}
|
||||
CryptoData = obj;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
@ -518,6 +522,69 @@ namespace BTCPayServer.Services.Invoices
|
||||
public Money NetworkFee { get; set; }
|
||||
}
|
||||
|
||||
public interface IPaymentMethod
|
||||
{
|
||||
string GetPaymentDestination();
|
||||
PaymentTypes GetPaymentType();
|
||||
Money GetTxFee();
|
||||
void SetPaymentDestination(string newPaymentDestination);
|
||||
}
|
||||
|
||||
public class CryptoDataId
|
||||
{
|
||||
public CryptoDataId(string cryptoCode, PaymentTypes paymentType)
|
||||
{
|
||||
if (cryptoCode == null)
|
||||
throw new ArgumentNullException(nameof(cryptoCode));
|
||||
PaymentType = paymentType;
|
||||
CryptoCode = cryptoCode;
|
||||
}
|
||||
public string CryptoCode { get; private set; }
|
||||
public PaymentTypes PaymentType { get; private set; }
|
||||
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
CryptoDataId item = obj as CryptoDataId;
|
||||
if (item == null)
|
||||
return false;
|
||||
return ToString().Equals(item.ToString(), StringComparison.InvariantCulture);
|
||||
}
|
||||
public static bool operator ==(CryptoDataId a, CryptoDataId b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
return true;
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
return false;
|
||||
return a.ToString() == b.ToString();
|
||||
}
|
||||
|
||||
public static bool operator !=(CryptoDataId a, CryptoDataId b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
#pragma warning disable CA1307 // Specify StringComparison
|
||||
return ToString().GetHashCode();
|
||||
#pragma warning restore CA1307 // Specify StringComparison
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (PaymentType == PaymentTypes.BTCLike)
|
||||
return CryptoCode;
|
||||
return CryptoCode + "_" + PaymentType.ToString();
|
||||
}
|
||||
|
||||
public static CryptoDataId Parse(string str)
|
||||
{
|
||||
var parts = str.Split('_');
|
||||
return new CryptoDataId(parts[0], parts.Length == 1 ? PaymentTypes.BTCLike : Enum.Parse<PaymentTypes>(parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
public class CryptoData
|
||||
{
|
||||
[JsonIgnore]
|
||||
@ -525,25 +592,99 @@ namespace BTCPayServer.Services.Invoices
|
||||
[JsonIgnore]
|
||||
public BTCPayNetwork Network { get; set; }
|
||||
[JsonProperty(PropertyName = "cryptoCode", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
[Obsolete("Use GetId().CryptoCode instead")]
|
||||
public string CryptoCode { get; set; }
|
||||
[JsonProperty(PropertyName = "paymentType", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
[Obsolete("Use GetId().PaymentType instead")]
|
||||
public string PaymentType { get; set; }
|
||||
|
||||
|
||||
public CryptoDataId GetId()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return new CryptoDataId(CryptoCode, string.IsNullOrEmpty(PaymentType) ? PaymentTypes.BTCLike : Enum.Parse<PaymentTypes>(PaymentType));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public void SetId(CryptoDataId id)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
CryptoCode = id.CryptoCode;
|
||||
PaymentType = id.PaymentType.ToString();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "rate")]
|
||||
public decimal Rate { get; set; }
|
||||
|
||||
[Obsolete("Use GetPaymentMethod() instead")]
|
||||
public JObject PaymentMethod { get; set; }
|
||||
public IPaymentMethod GetPaymentMethod()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// Legacy, old code does not have PaymentMethods
|
||||
if (string.IsNullOrEmpty(PaymentType) || PaymentMethod == null)
|
||||
{
|
||||
return new BitcoinLikeOnChainPaymentMethod()
|
||||
{
|
||||
FeeRate = FeeRate,
|
||||
DepositAddress = string.IsNullOrEmpty(DepositAddress) ? null : BitcoinAddress.Create(DepositAddress, Network?.NBitcoinNetwork),
|
||||
TxFee = TxFee
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (GetId().PaymentType == PaymentTypes.BTCLike)
|
||||
{
|
||||
var method = DeserializePaymentMethod<BitcoinLikeOnChainPaymentMethod>(PaymentMethod);
|
||||
method.TxFee = TxFee;
|
||||
method.DepositAddress = BitcoinAddress.Create(DepositAddress, Network?.NBitcoinNetwork);
|
||||
method.FeeRate = FeeRate;
|
||||
return method;
|
||||
}
|
||||
}
|
||||
throw new NotSupportedException(PaymentType);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
private T DeserializePaymentMethod<T>(JObject jobj) where T : class, IPaymentMethod
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(jobj.ToString());
|
||||
}
|
||||
|
||||
public void SetPaymentMethod(IPaymentMethod paymentMethod)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// Legacy, need to fill the old fields
|
||||
|
||||
if (PaymentType == null)
|
||||
PaymentType = paymentMethod.GetPaymentType().ToString();
|
||||
else if (PaymentType != paymentMethod.GetPaymentType().ToString())
|
||||
throw new InvalidOperationException("Invalid payment method affected");
|
||||
|
||||
if (paymentMethod is BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod)
|
||||
{
|
||||
TxFee = bitcoinPaymentMethod.TxFee;
|
||||
FeeRate = bitcoinPaymentMethod.FeeRate;
|
||||
DepositAddress = bitcoinPaymentMethod.DepositAddress.ToString();
|
||||
}
|
||||
var jobj = JObject.Parse(JsonConvert.SerializeObject(paymentMethod));
|
||||
PaymentMethod = jobj;
|
||||
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "feeRate")]
|
||||
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).FeeRate")]
|
||||
public FeeRate FeeRate { get; set; }
|
||||
[JsonProperty(PropertyName = "txFee")]
|
||||
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).TxFee")]
|
||||
public Money TxFee { get; set; }
|
||||
[JsonProperty(PropertyName = "depositAddress")]
|
||||
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).DepositAddress")]
|
||||
public string DepositAddress { get; set; }
|
||||
|
||||
public BitcoinAddress GetDepositAddress()
|
||||
{
|
||||
if (string.IsNullOrEmpty(DepositAddress))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return BitcoinAddress.Create(DepositAddress, Network.NBitcoinNetwork);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsPhantomBTC { get; set; }
|
||||
|
||||
@ -563,15 +704,15 @@ namespace BTCPayServer.Services.Invoices
|
||||
.OrderBy(p => p.ReceivedTime)
|
||||
.Select(_ =>
|
||||
{
|
||||
var txFee = _.GetValue(cryptoData, CryptoCode, cryptoData[_.GetCryptoCode()].TxFee);
|
||||
paid += _.GetValue(cryptoData, CryptoCode);
|
||||
var txFee = _.GetValue(cryptoData, GetId(), cryptoData[_.GetCryptoDataId()].GetTxFee());
|
||||
paid += _.GetValue(cryptoData, GetId());
|
||||
if (!paidEnough)
|
||||
{
|
||||
totalDue += txFee;
|
||||
paidTxFee += txFee;
|
||||
}
|
||||
paidEnough |= totalDue <= paid;
|
||||
if (CryptoCode == _.GetCryptoCode())
|
||||
if (GetId() == _.GetCryptoDataId())
|
||||
{
|
||||
cryptoPaid += _.GetCryptoPaymentData().GetValue();
|
||||
txCount++;
|
||||
@ -583,8 +724,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
if (!paidEnough)
|
||||
{
|
||||
txCount++;
|
||||
totalDue += TxFee;
|
||||
paidTxFee += TxFee;
|
||||
totalDue += GetTxFee();
|
||||
paidTxFee += GetTxFee();
|
||||
}
|
||||
var accounting = new CryptoDataAccounting();
|
||||
accounting.TotalDue = totalDue;
|
||||
@ -596,6 +737,13 @@ namespace BTCPayServer.Services.Invoices
|
||||
return accounting;
|
||||
}
|
||||
|
||||
private Money GetTxFee()
|
||||
{
|
||||
var method = GetPaymentMethod();
|
||||
if (method == null)
|
||||
return Money.Zero;
|
||||
return method.GetTxFee();
|
||||
}
|
||||
}
|
||||
|
||||
public class PaymentEntity
|
||||
@ -623,7 +771,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use GetCryptoCode() instead")]
|
||||
[Obsolete("Use GetCryptoDataId().CryptoCode instead")]
|
||||
public string CryptoCode
|
||||
{
|
||||
get;
|
||||
@ -632,15 +780,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
[Obsolete("Use GetCryptoPaymentData() instead")]
|
||||
public string CryptoPaymentData { get; set; }
|
||||
[Obsolete("Use GetCryptoPaymentDataType() instead")]
|
||||
[Obsolete("Use GetCryptoDataId().PaymentType instead")]
|
||||
public string CryptoPaymentDataType { get; set; }
|
||||
|
||||
public string GetCryptoPaymentDataType()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return String.IsNullOrEmpty(CryptoPaymentDataType) ? BitcoinLikePaymentData.OnchainBitcoinType : CryptoPaymentDataType;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public CryptoPaymentData GetCryptoPaymentData()
|
||||
{
|
||||
@ -656,7 +798,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
paymentData.Legacy = true;
|
||||
return paymentData;
|
||||
}
|
||||
if (CryptoPaymentDataType == BitcoinLikePaymentData.OnchainBitcoinType)
|
||||
if (GetCryptoDataId().PaymentType == PaymentTypes.BTCLike)
|
||||
{
|
||||
var paymentData = JsonConvert.DeserializeObject<BitcoinLikePaymentData>(CryptoPaymentData);
|
||||
// legacy
|
||||
@ -674,7 +816,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
#pragma warning disable CS0618
|
||||
if (cryptoPaymentData is BitcoinLikePaymentData paymentData)
|
||||
{
|
||||
CryptoPaymentDataType = BitcoinLikePaymentData.OnchainBitcoinType;
|
||||
// Legacy
|
||||
Outpoint = paymentData.Outpoint;
|
||||
Output = paymentData.Output;
|
||||
@ -682,16 +823,17 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException(cryptoPaymentData.ToString());
|
||||
CryptoPaymentDataType = paymentData.GetPaymentType().ToString();
|
||||
CryptoPaymentData = JsonConvert.SerializeObject(cryptoPaymentData);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
public Money GetValue(Dictionary<string, CryptoData> cryptoData, string cryptoCode, Money value = null)
|
||||
public Money GetValue(CryptoDataDictionary cryptoData, CryptoDataId cryptoDataId, Money value = null)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
value = value ?? Output.Value;
|
||||
#pragma warning restore CS0618
|
||||
var to = cryptoCode;
|
||||
var from = GetCryptoCode();
|
||||
var to = cryptoDataId;
|
||||
var from = this.GetCryptoDataId();
|
||||
if (to == from)
|
||||
return value;
|
||||
var fromRate = cryptoData[from].Rate;
|
||||
@ -702,6 +844,13 @@ namespace BTCPayServer.Services.Invoices
|
||||
return Money.Coins(otherCurrencyValue);
|
||||
}
|
||||
|
||||
public CryptoDataId GetCryptoDataId()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return new CryptoDataId(CryptoCode ?? "BTC", string.IsNullOrEmpty(CryptoPaymentDataType) ? PaymentTypes.BTCLike : Enum.Parse<PaymentTypes>(CryptoPaymentDataType));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public string GetCryptoCode()
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
@ -732,68 +881,4 @@ namespace BTCPayServer.Services.Invoices
|
||||
bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network);
|
||||
|
||||
}
|
||||
|
||||
public class BitcoinLikePaymentData : CryptoPaymentData
|
||||
{
|
||||
public readonly static string OnchainBitcoinType = "BTCLike";
|
||||
public BitcoinLikePaymentData()
|
||||
{
|
||||
|
||||
}
|
||||
public BitcoinLikePaymentData(Coin coin, bool rbf)
|
||||
{
|
||||
Outpoint = coin.Outpoint;
|
||||
Output = coin.TxOut;
|
||||
ConfirmationCount = 0;
|
||||
RBF = rbf;
|
||||
}
|
||||
[JsonIgnore]
|
||||
public OutPoint Outpoint { get; set; }
|
||||
[JsonIgnore]
|
||||
public TxOut Output { get; set; }
|
||||
public int ConfirmationCount { get; set; }
|
||||
public bool RBF { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is set to true if the payment was created before CryptoPaymentData existed in BTCPayServer
|
||||
/// </summary>
|
||||
public bool Legacy { get; set; }
|
||||
|
||||
public string GetPaymentId()
|
||||
{
|
||||
return Outpoint.ToString();
|
||||
}
|
||||
|
||||
public string[] GetSearchTerms()
|
||||
{
|
||||
return new[] { Outpoint.Hash.ToString() };
|
||||
}
|
||||
|
||||
public Money GetValue()
|
||||
{
|
||||
return Output.Value;
|
||||
}
|
||||
|
||||
public bool PaymentCompleted(PaymentEntity entity, BTCPayNetwork network)
|
||||
{
|
||||
return ConfirmationCount >= network.MaxTrackedConfirmation;
|
||||
}
|
||||
|
||||
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network)
|
||||
{
|
||||
if (speedPolicy == SpeedPolicy.HighSpeed)
|
||||
{
|
||||
return ConfirmationCount >= 1 || !RBF;
|
||||
}
|
||||
else if (speedPolicy == SpeedPolicy.MediumSpeed)
|
||||
{
|
||||
return ConfirmationCount >= 1;
|
||||
}
|
||||
else if (speedPolicy == SpeedPolicy.LowSpeed)
|
||||
{
|
||||
return ConfirmationCount >= 6;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,21 +123,25 @@ namespace BTCPayServer.Services.Invoices
|
||||
CustomerEmail = invoice.RefundMail
|
||||
});
|
||||
|
||||
foreach (var cryptoData in invoice.GetCryptoData(networkProvider).Values)
|
||||
foreach (var cryptoData in invoice.GetCryptoData(networkProvider))
|
||||
{
|
||||
if (cryptoData.Network == null)
|
||||
throw new InvalidOperationException("CryptoCode unsupported");
|
||||
var paymentDestination = cryptoData.GetPaymentMethod().GetPaymentDestination();
|
||||
|
||||
ScriptId hash = GetAddressInvoiceHash(cryptoData);
|
||||
context.AddressInvoices.Add(new AddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoice.Id,
|
||||
CreatedTime = DateTimeOffset.UtcNow,
|
||||
}.SetHash(cryptoData.GetDepositAddress().ScriptPubKey.Hash, cryptoData.CryptoCode));
|
||||
}.SetHash(hash, cryptoData.GetId().ToString()));
|
||||
|
||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoice.Id,
|
||||
Assigned = DateTimeOffset.UtcNow
|
||||
}.SetAddress(cryptoData.DepositAddress, cryptoData.CryptoCode));
|
||||
textSearch.Add(cryptoData.DepositAddress);
|
||||
}.SetAddress(paymentDestination, cryptoData.GetId().ToString()));
|
||||
textSearch.Add(paymentDestination);
|
||||
textSearch.Add(cryptoData.Calculate().TotalDue.ToString());
|
||||
}
|
||||
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id });
|
||||
@ -157,7 +161,17 @@ namespace BTCPayServer.Services.Invoices
|
||||
return invoice;
|
||||
}
|
||||
|
||||
public async Task<bool> NewAddress(string invoiceId, BitcoinAddress bitcoinAddress, BTCPayNetwork network)
|
||||
private static ScriptId GetAddressInvoiceHash(CryptoData cryptoData)
|
||||
{
|
||||
ScriptId hash = null;
|
||||
if (cryptoData.GetId().PaymentType == Payments.PaymentTypes.BTCLike)
|
||||
{
|
||||
hash = ((Payments.BitcoinLikeOnChainPaymentMethod)cryptoData.GetPaymentMethod()).DepositAddress.ScriptPubKey.Hash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public async Task<bool> NewAddress(string invoiceId, IPaymentMethod paymentMethod, BTCPayNetwork network)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
@ -166,17 +180,19 @@ namespace BTCPayServer.Services.Invoices
|
||||
return false;
|
||||
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob, network.NBitcoinNetwork);
|
||||
var currencyData = invoiceEntity.GetCryptoData(network, null);
|
||||
var currencyData = invoiceEntity.GetCryptoData(network, paymentMethod.GetPaymentType(), null);
|
||||
if (currencyData == null)
|
||||
return false;
|
||||
|
||||
if (currencyData.DepositAddress != null)
|
||||
var existingPaymentMethod = currencyData.GetPaymentMethod();
|
||||
if (existingPaymentMethod.GetPaymentDestination() != null)
|
||||
{
|
||||
MarkUnassigned(invoiceId, invoiceEntity, context, network.CryptoCode);
|
||||
MarkUnassigned(invoiceId, invoiceEntity, context, currencyData.GetId());
|
||||
}
|
||||
|
||||
currencyData.DepositAddress = bitcoinAddress.ToString();
|
||||
existingPaymentMethod.SetPaymentDestination(paymentMethod.GetPaymentDestination());
|
||||
|
||||
currencyData.SetPaymentMethod(existingPaymentMethod);
|
||||
#pragma warning disable CS0618
|
||||
if (network.IsBTC)
|
||||
{
|
||||
@ -191,15 +207,15 @@ namespace BTCPayServer.Services.Invoices
|
||||
InvoiceDataId = invoiceId,
|
||||
CreatedTime = DateTimeOffset.UtcNow
|
||||
}
|
||||
.SetHash(bitcoinAddress.ScriptPubKey.Hash, network.CryptoCode));
|
||||
.SetHash(GetAddressInvoiceHash(currencyData), network.CryptoCode));
|
||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoiceId,
|
||||
Assigned = DateTimeOffset.UtcNow
|
||||
}.SetAddress(bitcoinAddress.ToString(), network.CryptoCode));
|
||||
}.SetAddress(paymentMethod.GetPaymentDestination(), network.CryptoCode));
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
AddToTextSearch(invoice.Id, bitcoinAddress.ToString());
|
||||
AddToTextSearch(invoice.Id, paymentMethod.GetPaymentDestination());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -219,15 +235,15 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkUnassigned(string invoiceId, InvoiceEntity entity, ApplicationDbContext context, string cryptoCode)
|
||||
private static void MarkUnassigned(string invoiceId, InvoiceEntity entity, ApplicationDbContext context, CryptoDataId cryptoDataId)
|
||||
{
|
||||
foreach (var address in entity.GetCryptoData(null))
|
||||
{
|
||||
if (cryptoCode != null && cryptoCode != address.Value.CryptoCode)
|
||||
if (cryptoDataId != null && cryptoDataId != address.GetId())
|
||||
continue;
|
||||
var historical = new HistoricalAddressInvoiceData();
|
||||
historical.InvoiceDataId = invoiceId;
|
||||
historical.SetAddress(address.Value.DepositAddress, address.Value.CryptoCode);
|
||||
historical.SetAddress(address.GetPaymentMethod().GetPaymentDestination(), address.GetId().ToString());
|
||||
historical.UnAssigned = DateTimeOffset.UtcNow;
|
||||
context.Attach(historical);
|
||||
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
||||
|
@ -613,7 +613,7 @@
|
||||
<div style="text-align:center">
|
||||
@foreach(var crypto in Model.AvailableCryptos)
|
||||
{
|
||||
<a style="text-decoration:none;" href="@crypto.Link" onclick="srvModel.cryptoCode='@crypto.CryptoCode'; fetchStatus(); return false;"><img style="height:32px; margin-right:5px; margin-left:5px;" alt="@crypto.CryptoCode" src="@crypto.CryptoImage" /></a>
|
||||
<a style="text-decoration:none;" href="@crypto.Link" onclick="srvModel.cryptoDataId='@crypto.CryptoDataId'; fetchStatus(); return false;"><img style="height:32px; margin-right:5px; margin-left:5px;" alt="@crypto.CryptoDataId" src="@crypto.CryptoImage" /></a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ function onDataCallback(jsonData) {
|
||||
}
|
||||
|
||||
function fetchStatus() {
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/" + srvModel.cryptoCode + "/status";
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/" + srvModel.cryptoDataId + "/status";
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: "GET"
|
||||
|
Loading…
Reference in New Issue
Block a user