Big refactoring for supporting new type of payment

This commit is contained in:
nicolas.dorier 2018-02-19 02:38:03 +09:00
parent 752133b01c
commit aa4519ac30
17 changed files with 540 additions and 214 deletions

View File

@ -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);

View File

@ -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" />

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"));

View File

@ -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);

View File

@ -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; }
}
}

View 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;
}
}
}

View 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
}
}

View 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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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>
}

View File

@ -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"