BlockExplorer links should be using payment method ids (#6273)

This commit is contained in:
Nicolas Dorier
2024-10-04 16:58:13 +09:00
committed by GitHub
parent c3e51f51b6
commit 5704919b3a
30 changed files with 130 additions and 85 deletions

View File

@@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NBitcoin; using NBitcoin;
using NBXplorer;
using NBXplorer.Client; using NBXplorer.Client;
using static BTCPayServer.Components.StoreRecentTransactions.StoreRecentTransactionsViewModel; using static BTCPayServer.Components.StoreRecentTransactions.StoreRecentTransactionsViewModel;
@@ -80,7 +81,7 @@ public class StoreRecentTransactions : ViewComponent
Balance = tx.BalanceChange.ShowMoney(network), Balance = tx.BalanceChange.ShowMoney(network),
Currency = vm.CryptoCode, Currency = vm.CryptoCode,
IsConfirmed = tx.Confirmations != 0, IsConfirmed = tx.Confirmations != 0,
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, tx.TransactionId.ToString()), Link = _transactionLinkProviders.GetTransactionLink(pmi, tx.TransactionId.ToString()),
Timestamp = tx.SeenAt, Timestamp = tx.SeenAt,
Labels = labels Labels = labels
}; };

View File

@@ -334,7 +334,7 @@ namespace BTCPayServer.Controllers.Greenfield
#pragma warning disable CS0612 // Type or member is obsolete #pragma warning disable CS0612 // Type or member is obsolete
Labels = info?.LegacyLabels ?? new Dictionary<string, LabelData>(), Labels = info?.LegacyLabels ?? new Dictionary<string, LabelData>(),
#pragma warning restore CS0612 // Type or member is obsolete #pragma warning restore CS0612 // Type or member is obsolete
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, coin.OutPoint.ToString()), Link = _transactionLinkProviders.GetTransactionLink(pmi, coin.OutPoint.ToString()),
Timestamp = coin.Timestamp, Timestamp = coin.Timestamp,
KeyPath = coin.KeyPath, KeyPath = coin.KeyPath,
Confirmations = coin.Confirmations, Confirmations = coin.Confirmations,

View File

@@ -362,7 +362,7 @@ namespace BTCPayServer.Controllers
return View(settings); return View(settings);
} }
settings.BlockExplorerLinks = settings.BlockExplorerLinks settings.BlockExplorerLinks = settings.BlockExplorerLinks
.Where(tuple => _transactionLinkProviders.GetDefaultBlockExplorerLink(tuple.CryptoCode) != tuple.Link) .Where(tuple => _transactionLinkProviders.GetDefaultBlockExplorerLink(tuple.PaymentMethodId) != tuple.Link)
.Where(tuple => tuple.Link is not null) .Where(tuple => tuple.Link is not null)
.ToList(); .ToList();

View File

@@ -266,7 +266,7 @@ namespace BTCPayServer.Controllers
{ {
var vm = new ListTransactionsViewModel.TransactionViewModel(); var vm = new ListTransactionsViewModel.TransactionViewModel();
vm.Id = tx.TransactionId.ToString(); vm.Id = tx.TransactionId.ToString();
vm.Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, vm.Id); vm.Link = _transactionLinkProviders.GetTransactionLink(pmi, vm.Id);
vm.Timestamp = tx.SeenAt; vm.Timestamp = tx.SeenAt;
vm.Positive = tx.BalanceChange.GetValue(wallet.Network) >= 0; vm.Positive = tx.BalanceChange.GetValue(wallet.Network) >= 0;
vm.Balance = tx.BalanceChange.ShowMoney(wallet.Network); vm.Balance = tx.BalanceChange.ShowMoney(wallet.Network);
@@ -600,7 +600,7 @@ namespace BTCPayServer.Controllers
Amount = coin.Value.GetValue(network), Amount = coin.Value.GetValue(network),
Comment = info?.Comment, Comment = info?.Comment,
Labels = _labelService.CreateTransactionTagModels(info, Request), Labels = _labelService.CreateTransactionTagModels(info, Request),
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, coin.OutPoint.ToString()), Link = _transactionLinkProviders.GetTransactionLink(pmi, coin.OutPoint.ToString()),
Confirmations = coin.Confirmations Confirmations = coin.Confirmations
}; };
}).ToArray(); }).ToArray();

View File

@@ -126,7 +126,6 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
var payoutMethodId = payout.GetPayoutMethodId(); var payoutMethodId = payout.GetPayoutMethodId();
if (payoutMethodId is null) if (payoutMethodId is null)
return null; return null;
var cryptoCode = Network.CryptoCode;
ParseProofType(payout.Proof, out var raw, out var proofType); ParseProofType(payout.Proof, out var raw, out var proofType);
if (proofType == PayoutTransactionOnChainBlob.Type) if (proofType == PayoutTransactionOnChainBlob.Type)
{ {
@@ -135,7 +134,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(payoutMethodId))); JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(payoutMethodId)));
if (res == null) if (res == null)
return null; return null;
res.LinkTemplate = _transactionLinkProviders.GetBlockExplorerLink(cryptoCode); res.LinkTemplate = _transactionLinkProviders.GetBlockExplorerLink(PaymentTypes.CHAIN.GetPaymentMethodId(Network.CryptoCode));
return res; return res;
} }
return raw.ToObject<ManualPayoutProof>(); return raw.ToObject<ManualPayoutProof>();

View File

@@ -662,10 +662,13 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
} }
return services; return services;
} }
public static void AddTransactionLinkProvider(this IServiceCollection services, string cryptoCode, TransactionLinkProvider provider) public static void AddTransactionLinkProvider(this IServiceCollection services, PaymentMethodId paymentMethodId, TransactionLinkProvider provider)
{ {
services.AddSingleton<TransactionLinkProviders.Entry>(new TransactionLinkProviders.Entry(cryptoCode, provider)); services.AddSingleton<TransactionLinkProviders.Entry>(new TransactionLinkProviders.Entry(paymentMethodId, provider));
} }
[Obsolete("Use AddTransactionLinkProvider(services, PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), provider) instead")]
public static void AddTransactionLinkProvider(this IServiceCollection services, string cryptoCode, TransactionLinkProvider provider) =>
AddTransactionLinkProvider(services, PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), provider);
public static void AddRateProviderExchangeSharp<T>(this IServiceCollection services, RateSourceInfo rateInfo) where T : ExchangeSharp.ExchangeAPI public static void AddRateProviderExchangeSharp<T>(this IServiceCollection services, RateSourceInfo rateInfo) where T : ExchangeSharp.ExchangeAPI
{ {
services.AddSingleton<IRateProvider, ExchangeSharpRateProvider<T>>(o => services.AddSingleton<IRateProvider, ExchangeSharpRateProvider<T>>(o =>

View File

@@ -211,6 +211,12 @@ namespace BTCPayServer.Hosting
settings.MigrateToStoreConfig = true; settings.MigrateToStoreConfig = true;
await _Settings.UpdateSetting(settings); await _Settings.UpdateSetting(settings);
} }
if (!settings.MigrateBlockExplorerLinks)
{
await MigrateBlockExplorerLinks();
settings.MigrateBlockExplorerLinks = true;
await _Settings.UpdateSetting(settings);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -219,6 +225,28 @@ namespace BTCPayServer.Hosting
} }
} }
private async Task MigrateBlockExplorerLinks()
{
await using var ctx = _DBContextFactory.CreateContext();
var settings = await ctx.Settings.Where(s => s.Id == "BTCPayServer.Services.PoliciesSettings").FirstOrDefaultAsync();
if (settings is null)
return;
var obj = JObject.Parse(settings.Value);
var arr = obj["BlockExplorerLinks"] as JArray;
if (arr is null or { Count: 0 })
return;
foreach (var item in arr.OfType<JObject>())
{
var cryptoCode = item["CryptoCode"]?.Value<string>();
if (cryptoCode is null)
continue;
item.Remove("CryptoCode");
item["PaymentMethodId"] = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode).ToString();
}
settings.Value = obj.ToString();
await ctx.SaveChangesAsync();
}
private async Task MigrateToStoreConfig() private async Task MigrateToStoreConfig()
{ {
await using var ctx = _DBContextFactory.CreateContext(); await using var ctx = _DBContextFactory.CreateContext();
@@ -315,33 +343,33 @@ namespace BTCPayServer.Hosting
private async Task MigrateAppYmlToJson() private async Task MigrateAppYmlToJson()
{ {
await using var ctx = _DBContextFactory.CreateContext(); await using var ctx = _DBContextFactory.CreateContext();
var apps = await ctx.Apps.Where(data => CrowdfundAppType.AppType == data.AppType || PointOfSaleAppType.AppType == data.AppType) var apps = await ctx.Apps.Where(data => CrowdfundAppType.AppType == data.AppType || PointOfSaleAppType.AppType == data.AppType)
.ToListAsync(); .ToListAsync();
foreach (var app in apps) foreach (var app in apps)
{ {
switch (app.AppType) switch (app.AppType)
{ {
case CrowdfundAppType.AppType : case CrowdfundAppType.AppType:
var cfSettings = app.GetSettings<CrowdfundSettings>(); var cfSettings = app.GetSettings<CrowdfundSettings>();
if (!string.IsNullOrEmpty(cfSettings?.PerksTemplate)) if (!string.IsNullOrEmpty(cfSettings?.PerksTemplate))
{ {
cfSettings.PerksTemplate = AppService.SerializeTemplate(ParsePOSYML(cfSettings?.PerksTemplate)); cfSettings.PerksTemplate = AppService.SerializeTemplate(ParsePOSYML(cfSettings?.PerksTemplate));
app.SetSettings(cfSettings); app.SetSettings(cfSettings);
} }
break; break;
case PointOfSaleAppType.AppType: case PointOfSaleAppType.AppType:
var pSettings = app.GetSettings<PointOfSaleSettings>(); var pSettings = app.GetSettings<PointOfSaleSettings>();
if (!string.IsNullOrEmpty(pSettings?.Template)) if (!string.IsNullOrEmpty(pSettings?.Template))
{ {
pSettings.Template = AppService.SerializeTemplate(ParsePOSYML(pSettings?.Template)); pSettings.Template = AppService.SerializeTemplate(ParsePOSYML(pSettings?.Template));
app.SetSettings(pSettings); app.SetSettings(pSettings);
} }
break; break;
} }
} }
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
public static ViewPointOfSaleViewModel.Item[] ParsePOSYML(string yaml) public static ViewPointOfSaleViewModel.Item[] ParsePOSYML(string yaml)
{ {
@@ -349,10 +377,10 @@ namespace BTCPayServer.Hosting
var stream = new YamlStream(); var stream = new YamlStream();
if (string.IsNullOrEmpty(yaml)) if (string.IsNullOrEmpty(yaml))
return items.ToArray(); return items.ToArray();
stream.Load(new StringReader(yaml)); stream.Load(new StringReader(yaml));
if(stream.Documents.FirstOrDefault()?.RootNode is not YamlMappingNode root) if (stream.Documents.FirstOrDefault()?.RootNode is not YamlMappingNode root)
return items.ToArray(); return items.ToArray();
foreach (var posItem in root.Children) foreach (var posItem in root.Children)
{ {
@@ -364,12 +392,14 @@ namespace BTCPayServer.Hosting
var currentItem = new ViewPointOfSaleViewModel.Item var currentItem = new ViewPointOfSaleViewModel.Item
{ {
Id = trimmedKey, Title = trimmedKey, PriceType = ViewPointOfSaleViewModel.ItemPriceType.Fixed Id = trimmedKey,
Title = trimmedKey,
PriceType = ViewPointOfSaleViewModel.ItemPriceType.Fixed
}; };
var itemSpecs = (YamlMappingNode)posItem.Value; var itemSpecs = (YamlMappingNode)posItem.Value;
foreach (var spec in itemSpecs) foreach (var spec in itemSpecs)
{ {
if (spec.Key is not YamlScalarNode {Value: string keyString} || string.IsNullOrEmpty(keyString)) if (spec.Key is not YamlScalarNode { Value: string keyString } || string.IsNullOrEmpty(keyString))
continue; continue;
var scalarValue = spec.Value as YamlScalarNode; var scalarValue = spec.Value as YamlScalarNode;
switch (keyString) switch (keyString)

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Models.InvoicingModels
{ {
public class OnchainPaymentViewModel public class OnchainPaymentViewModel
{ {
public string Crypto { get; set; } public PaymentMethodId PaymentMethodId { get; set; }
public string Confirmations { get; set; } public string Confirmations { get; set; }
public BitcoinAddress DepositAddress { get; set; } public BitcoinAddress DepositAddress { get; set; }
public string Amount { get; set; } public string Amount { get; set; }
@@ -26,6 +26,7 @@ namespace BTCPayServer.Models.InvoicingModels
public string AdditionalInformation { get; set; } public string AdditionalInformation { get; set; }
public decimal NetworkFee { get; set; } public decimal NetworkFee { get; set; }
public string PaymentProof { get; set; } public string PaymentProof { get; set; }
public string Currency { get; set; }
} }
public class OffChainPaymentViewModel public class OffChainPaymentViewModel

View File

@@ -213,9 +213,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
} }
string txId = paymentEntity.Id; string txId = paymentEntity.Id;
// TODO: Move that in an extension string link = paymentMethodId is null ? null : txLinkProvider.GetTransactionLink(paymentMethodId, txId);
var cryptoCode = handlers.TryGetNetwork(paymentMethodId)?.CryptoCode;
string link = cryptoCode is null ? null : txLinkProvider.GetTransactionLink(cryptoCode, txId);
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
{ {

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Payments;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
@@ -9,7 +10,7 @@ namespace BTCPayServer.Models.StoreReportsViewModels;
public class StoreReportsViewModel public class StoreReportsViewModel
{ {
public string InvoiceTemplateUrl { get; set; } public string InvoiceTemplateUrl { get; set; }
public Dictionary<string,string> ExplorerTemplateUrls { get; set; } public Dictionary<PaymentMethodId,string> ExplorerTemplateUrls { get; set; }
public StoreReportRequest Request { get; set; } public StoreReportRequest Request { get; set; }
public List<string> AvailableViews { get; set; } public List<string> AvailableViews { get; set; }
public StoreReportResponse Result { get; set; } public StoreReportResponse Result { get; set; }

View File

@@ -30,6 +30,6 @@ public partial class AltcoinsPlugin
}.SetDefaultElectrumMapping(ChainName); }.SetDefaultElectrumMapping(ChainName);
var blockExplorerLink = ChainName == ChainName.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}"; var blockExplorerLink = ChainName == ChainName.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}";
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(blockExplorerLink)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(blockExplorerLink));
} }
} }

View File

@@ -33,6 +33,6 @@ public partial class AltcoinsPlugin
? "https://insight.dash.org/insight/tx/{0}" ? "https://insight.dash.org/insight/tx/{0}"
: "https://testnet-insight.dashevo.org/insight/tx/{0}"; : "https://testnet-insight.dashevo.org/insight/tx/{0}";
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(blockExplorerLink)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(blockExplorerLink));
} }
} }

View File

@@ -30,7 +30,7 @@ public partial class AltcoinsPlugin
var blockExplorerLink = ChainName == ChainName.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}"; var blockExplorerLink = ChainName == ChainName.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}";
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(blockExplorerLink)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(blockExplorerLink));
} }
} }

View File

@@ -34,7 +34,7 @@ public partial class AltcoinsPlugin
? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm" ? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm"
: "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm"; : "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm";
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(blockExplorerLink)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(blockExplorerLink));
} }
} }

View File

@@ -47,7 +47,7 @@ public partial class AltcoinsPlugin
? "https://live.blockcypher.com/ltc/tx/{0}/" ? "https://live.blockcypher.com/ltc/tx/{0}/"
: "http://explorer.litecointools.com/tx/{0}"; : "http://explorer.litecointools.com/tx/{0}";
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(blockExplorerLinks)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(blockExplorerLinks));
} }
} }

View File

@@ -31,7 +31,7 @@ public partial class AltcoinsPlugin
var blockExplorerLink = ChainName == ChainName.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}"; var blockExplorerLink = ChainName == ChainName.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}";
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(blockExplorerLink)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(blockExplorerLink));
} }
} }

View File

@@ -32,6 +32,6 @@ public partial class AltcoinsPlugin
var blockExplorerLink = ChainName == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}"; var blockExplorerLink = ChainName == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}";
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(blockExplorerLink)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(blockExplorerLink));
} }
} }

View File

@@ -32,7 +32,7 @@ public partial class AltcoinsPlugin
SupportLightning = false SupportLightning = false
}.SetDefaultElectrumMapping(ChainName); }.SetDefaultElectrumMapping(ChainName);
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(LiquidBlockExplorer)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(LiquidBlockExplorer));
selectedChains.Add("LBTC"); selectedChains.Add("LBTC");
} }
@@ -61,7 +61,7 @@ public partial class AltcoinsPlugin
}.SetDefaultElectrumMapping(ChainName); }.SetDefaultElectrumMapping(ChainName);
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(LiquidBlockExplorer)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(LiquidBlockExplorer));
selectedChains.Add("LBTC"); selectedChains.Add("LBTC");
} }
@@ -90,7 +90,7 @@ public partial class AltcoinsPlugin
SupportLightning = false SupportLightning = false
}.SetDefaultElectrumMapping(ChainName); }.SetDefaultElectrumMapping(ChainName);
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(nbxplorerNetwork.CryptoCode, new DefaultTransactionLinkProvider(LiquidBlockExplorer)); .AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), new DefaultTransactionLinkProvider(LiquidBlockExplorer));
selectedChains.Add("LBTC"); selectedChains.Add("LBTC");
} }

View File

@@ -41,7 +41,7 @@ public partial class AltcoinsPlugin
: "https://testnet.xmrchain.net/tx/{0}"; : "https://testnet.xmrchain.net/tx/{0}";
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("XMR"); var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("XMR");
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(network.CryptoCode, new SimpleTransactionLinkProvider(blockExplorerLink)); .AddTransactionLinkProvider(pmi, new SimpleTransactionLinkProvider(blockExplorerLink));
services.AddSingleton<IPaymentMethodViewExtension>(provider => services.AddSingleton<IPaymentMethodViewExtension>(provider =>
(IPaymentMethodViewExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodViewExtension), new object[] { pmi })); (IPaymentMethodViewExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodViewExtension), new object[] { pmi }));

View File

@@ -42,7 +42,7 @@ public partial class AltcoinsPlugin
: "https://testnet.xmrchain.net/tx/{0}"; : "https://testnet.xmrchain.net/tx/{0}";
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("ZEC"); var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("ZEC");
services.AddBTCPayNetwork(network) services.AddBTCPayNetwork(network)
.AddTransactionLinkProvider(network.CryptoCode, new SimpleTransactionLinkProvider(blockExplorerLink)); .AddTransactionLinkProvider(pmi, new SimpleTransactionLinkProvider(blockExplorerLink));
services.AddSingleton<IPaymentMethodViewExtension>(provider => services.AddSingleton<IPaymentMethodViewExtension>(provider =>
(IPaymentMethodViewExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodViewExtension), new object[] { pmi })); (IPaymentMethodViewExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodViewExtension), new object[] { pmi }));

View File

@@ -1,6 +1,7 @@
#nullable enable #nullable enable
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Hosting; using BTCPayServer.Hosting;
using BTCPayServer.Payments;
using BTCPayServer.Services; using BTCPayServer.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NBitcoin; using NBitcoin;
@@ -47,7 +48,7 @@ namespace BTCPayServer.Plugins.Bitcoin
}.SetDefaultElectrumMapping(chainName); }.SetDefaultElectrumMapping(chainName);
applicationBuilder.AddBTCPayNetwork(network); applicationBuilder.AddBTCPayNetwork(network);
applicationBuilder.AddTransactionLinkProvider(network.CryptoCode, defaultTransactionLinkProvider); applicationBuilder.AddTransactionLinkProvider(PaymentTypes.CHAIN.GetPaymentMethodId(nbxplorerNetwork.CryptoCode), defaultTransactionLinkProvider);
} }
} }
} }

View File

@@ -1,15 +1,17 @@
using System; using System;
using BTCPayServer.Payments;
namespace BTCPayServer.Services.Altcoins.Monero.UI namespace BTCPayServer.Services.Altcoins.Monero.UI
{ {
public class MoneroPaymentViewModel public class MoneroPaymentViewModel
{ {
public string Crypto { get; set; } public PaymentMethodId PaymentMethodId { get; set; }
public string Confirmations { get; set; } public string Confirmations { get; set; }
public string DepositAddress { get; set; } public string DepositAddress { get; set; }
public string Amount { get; set; } public string Amount { get; set; }
public string TransactionId { get; set; } public string TransactionId { get; set; }
public DateTimeOffset ReceivedTime { get; set; } public DateTimeOffset ReceivedTime { get; set; }
public string TransactionLink { get; set; } public string TransactionLink { get; set; }
public string Currency { get; set; }
} }
} }

View File

@@ -1,15 +1,17 @@
using System; using System;
using BTCPayServer.Payments;
namespace BTCPayServer.Services.Altcoins.Zcash.UI namespace BTCPayServer.Services.Altcoins.Zcash.UI
{ {
public class ZcashPaymentViewModel public class ZcashPaymentViewModel
{ {
public string Crypto { get; set; } public PaymentMethodId PaymentMethodId { get; set; }
public string Confirmations { get; set; } public string Confirmations { get; set; }
public string DepositAddress { get; set; } public string DepositAddress { get; set; }
public string Amount { get; set; } public string Amount { get; set; }
public string TransactionId { get; set; } public string TransactionId { get; set; }
public DateTimeOffset ReceivedTime { get; set; } public DateTimeOffset ReceivedTime { get; set; }
public string TransactionLink { get; set; } public string TransactionLink { get; set; }
public string Currency { get; set; }
} }
} }

View File

@@ -31,5 +31,6 @@ namespace BTCPayServer.Services
public bool FixMappedDomainAppType { get; set; } public bool FixMappedDomainAppType { get; set; }
public bool MigrateAppYmlToJson { get; set; } public bool MigrateAppYmlToJson { get; set; }
public bool MigrateToStoreConfig { get; set; } public bool MigrateToStoreConfig { get; set; }
public bool MigrateBlockExplorerLinks { get; set; }
} }
} }

View File

@@ -1,8 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using BTCPayServer.JsonConverters;
using BTCPayServer.Payments;
using BTCPayServer.Validation; using BTCPayServer.Validation;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services namespace BTCPayServer.Services
{ {
@@ -98,7 +101,8 @@ namespace BTCPayServer.Services
public class BlockExplorerOverrideItem public class BlockExplorerOverrideItem
{ {
public string CryptoCode { get; set; } [JsonConverter(typeof(PaymentMethodIdJsonConverter))]
public PaymentMethodId PaymentMethodId { get; set; }
public string Link { get; set; } public string Link { get; set; }
} }

View File

@@ -8,16 +8,16 @@ using System.Linq;
namespace BTCPayServer.Services; namespace BTCPayServer.Services;
public class TransactionLinkProviders : Dictionary<string, TransactionLinkProvider> public class TransactionLinkProviders : Dictionary<PaymentMethodId, TransactionLinkProvider>
{ {
public SettingsRepository SettingsRepository { get; } public SettingsRepository SettingsRepository { get; }
public record Entry(string CryptoCode, TransactionLinkProvider Provider); public record Entry(PaymentMethodId PaymentMethodId, TransactionLinkProvider Provider);
public TransactionLinkProviders(IEnumerable<Entry> entries, SettingsRepository settingsRepository) public TransactionLinkProviders(IEnumerable<Entry> entries, SettingsRepository settingsRepository)
{ {
foreach (var e in entries) foreach (var e in entries)
{ {
TryAdd(e.CryptoCode, e.Provider); TryAdd(e.PaymentMethodId, e.Provider);
} }
SettingsRepository = settingsRepository; SettingsRepository = settingsRepository;
} }
@@ -29,29 +29,28 @@ public class TransactionLinkProviders : Dictionary<string, TransactionLinkProvid
{ {
foreach ((var pmi, var prov) in this) foreach ((var pmi, var prov) in this)
{ {
var overrideLink = links.FirstOrDefault(item => var overrideLink = links.FirstOrDefault(item => item.PaymentMethodId == pmi);
item.CryptoCode.Equals(pmi, StringComparison.InvariantCultureIgnoreCase));
prov.OverrideBlockExplorerLink = overrideLink?.Link ?? prov.BlockExplorerLinkDefault; prov.OverrideBlockExplorerLink = overrideLink?.Link ?? prov.BlockExplorerLinkDefault;
} }
} }
} }
public string? GetTransactionLink(string cryptoCode, string paymentId) public string? GetTransactionLink(PaymentMethodId paymentMethodId, string paymentId)
{ {
ArgumentNullException.ThrowIfNull(cryptoCode); ArgumentNullException.ThrowIfNull(paymentMethodId);
ArgumentNullException.ThrowIfNull(paymentId); ArgumentNullException.ThrowIfNull(paymentId);
TryGetValue(cryptoCode, out var p); TryGetValue(paymentMethodId, out var p);
return p?.GetTransactionLink(paymentId); return p?.GetTransactionLink(paymentId);
} }
public string? GetBlockExplorerLink(string cryptoCode) public string? GetBlockExplorerLink(PaymentMethodId paymentMethodId)
{ {
TryGetValue(cryptoCode, out var p); TryGetValue(paymentMethodId, out var p);
return p?.BlockExplorerLink; return p?.BlockExplorerLink;
} }
public string? GetDefaultBlockExplorerLink(string cryptoCode) public string? GetDefaultBlockExplorerLink(PaymentMethodId paymentMethodId)
{ {
TryGetValue(cryptoCode, out var p); TryGetValue(paymentMethodId, out var p);
return p?.BlockExplorerLinkDefault; return p?.BlockExplorerLinkDefault;
} }
} }

View File

@@ -18,7 +18,7 @@
var network = handler.Network; var network = handler.Network;
var m = new OnchainPaymentViewModel(); var m = new OnchainPaymentViewModel();
var onChainPaymentData = handler.ParsePaymentDetails(payment.Details); var onChainPaymentData = handler.ParsePaymentDetails(payment.Details);
m.Crypto = network.CryptoCode; m.PaymentMethodId = payment.PaymentMethodId;
m.DepositAddress = BitcoinAddress.Create(payment.Destination, network.NBitcoinNetwork); m.DepositAddress = BitcoinAddress.Create(payment.Destination, network.NBitcoinNetwork);
var confReq = NBXplorerListener.ConfirmationRequired(payment.InvoiceEntity, onChainPaymentData); var confReq = NBXplorerListener.ConfirmationRequired(payment.InvoiceEntity, onChainPaymentData);
@@ -37,12 +37,13 @@
} }
m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString(); m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString();
m.ReceivedTime = payment.ReceivedTime; m.ReceivedTime = payment.ReceivedTime;
m.TransactionLink = TransactionLinkProviders.GetTransactionLink(network.CryptoCode, onChainPaymentData.Outpoint.ToString()); m.TransactionLink = TransactionLinkProviders.GetTransactionLink(payment.PaymentMethodId, onChainPaymentData.Outpoint.ToString());
m.Replaced = !payment.Accounted; m.Replaced = !payment.Accounted;
m.CryptoPaymentData = onChainPaymentData; m.CryptoPaymentData = onChainPaymentData;
m.Value = payment.Value; m.Value = payment.Value;
m.NetworkFee = payment.PaymentMethodFee; m.NetworkFee = payment.PaymentMethodFee;
m.PaymentProof = onChainPaymentData.Outpoint.ToString(); m.PaymentProof = onChainPaymentData.Outpoint.ToString();
m.Currency = payment.Currency;
return m; return m;
}) })
.Where(model => model != null) .Where(model => model != null)
@@ -58,7 +59,7 @@
<table class="table table-hover mb-0"> <table class="table table-hover mb-0">
<thead> <thead>
<tr> <tr>
<th class="w-75px">Crypto</th> <th class="w-75px">Payment Method</th>
<th class="w-100px">Index</th> <th class="w-100px">Index</th>
<th class="w-175px">Destination</th> <th class="w-175px">Destination</th>
<th class="text-nowrap">Payment Proof</th> <th class="text-nowrap">Payment Proof</th>
@@ -79,7 +80,7 @@
@foreach (var payment in payments) @foreach (var payment in payments)
{ {
<tr style="@(payment.Replaced ? "text-decoration: line-through" : "")"> <tr style="@(payment.Replaced ? "text-decoration: line-through" : "")">
<td>@payment.Crypto</td> <td>@payment.PaymentMethodId</td>
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? "Unknown")</td> <td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? "Unknown")</td>
<td> <td>
<vc:truncate-center text="@payment.DepositAddress.ToString()" classes="truncate-center-id" /> <vc:truncate-center text="@payment.DepositAddress.ToString()" classes="truncate-center-id" />
@@ -93,7 +94,7 @@
} }
<td class="text-end">@payment.Confirmations</td> <td class="text-end">@payment.Confirmations</td>
<td class="payment-value text-end text-nowrap"> <td class="payment-value text-end text-nowrap">
<span data-sensitive class="text-success">@DisplayFormatter.Currency(payment.Value, payment.Crypto)</span> <span data-sensitive class="text-success">@DisplayFormatter.Currency(payment.Value, payment.Currency)</span>
@if (!string.IsNullOrEmpty(payment.AdditionalInformation)) @if (!string.IsNullOrEmpty(payment.AdditionalInformation))
{ {
<div>(@payment.AdditionalInformation)</div> <div>(@payment.AdditionalInformation)</div>

View File

@@ -17,7 +17,7 @@
return null; return null;
var m = new MoneroPaymentViewModel(); var m = new MoneroPaymentViewModel();
var onChainPaymentData = handler.ParsePaymentDetails(payment.Details); var onChainPaymentData = handler.ParsePaymentDetails(payment.Details);
m.Crypto = handler.Network.CryptoCode; m.PaymentMethodId = handler.PaymentMethodId;
m.DepositAddress = payment.Destination; m.DepositAddress = payment.Destination;
m.Amount = payment.Value.ToString(CultureInfo.InvariantCulture); m.Amount = payment.Value.ToString(CultureInfo.InvariantCulture);
@@ -28,7 +28,8 @@
m.TransactionId = onChainPaymentData.TransactionId; m.TransactionId = onChainPaymentData.TransactionId;
m.ReceivedTime = payment.ReceivedTime; m.ReceivedTime = payment.ReceivedTime;
m.TransactionLink = TransactionLinkProviders.GetTransactionLink(m.Crypto, onChainPaymentData.TransactionId); m.TransactionLink = TransactionLinkProviders.GetTransactionLink(m.PaymentMethodId, onChainPaymentData.TransactionId);
m.Currency = payment.Currency;
return m; return m;
}).Where(c => c != null).ToList(); }).Where(c => c != null).ToList();
} }
@@ -40,7 +41,7 @@
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th class="w-75px">Crypto</th> <th class="w-75px">Payment Method</th>
<th class="w-175px">Destination</th> <th class="w-175px">Destination</th>
<th class="text-nowrap">Payment Proof</th> <th class="text-nowrap">Payment Proof</th>
<th class="text-end">Confirmations</th> <th class="text-end">Confirmations</th>
@@ -51,12 +52,12 @@
@foreach (var payment in payments) @foreach (var payment in payments)
{ {
<tr > <tr >
<td>@payment.Crypto</td> <td>@payment.PaymentMethodId</td>
<td><vc:truncate-center text="@payment.DepositAddress" classes="truncate-center-id" /></td> <td><vc:truncate-center text="@payment.DepositAddress" classes="truncate-center-id" /></td>
<td><vc:truncate-center text="@payment.TransactionId" link="@payment.TransactionLink" classes="truncate-center-id" /></td> <td><vc:truncate-center text="@payment.TransactionId" link="@payment.TransactionLink" classes="truncate-center-id" /></td>
<td class="text-end">@payment.Confirmations</td> <td class="text-end">@payment.Confirmations</td>
<td class="payment-value text-end text-nowrap"> <td class="payment-value text-end text-nowrap">
<span data-sensitive class="text-success">@DisplayFormatter.Currency(payment.Amount, payment.Crypto)</span> <span data-sensitive class="text-success">@DisplayFormatter.Currency(payment.Amount, payment.Currency)</span>
</td> </td>
</tr> </tr>
} }

View File

@@ -18,7 +18,7 @@
return null; return null;
var m = new ZcashPaymentViewModel(); var m = new ZcashPaymentViewModel();
var onChainPaymentData = handler.ParsePaymentDetails(payment.Details); var onChainPaymentData = handler.ParsePaymentDetails(payment.Details);
m.Crypto = handler.Network.CryptoCode; m.PaymentMethodId = handler.PaymentMethodId;
m.DepositAddress = payment.Destination; m.DepositAddress = payment.Destination;
m.Amount = payment.Value.ToString(CultureInfo.InvariantCulture); m.Amount = payment.Value.ToString(CultureInfo.InvariantCulture);
@@ -29,7 +29,8 @@
m.TransactionId = onChainPaymentData.TransactionId; m.TransactionId = onChainPaymentData.TransactionId;
m.ReceivedTime = payment.ReceivedTime; m.ReceivedTime = payment.ReceivedTime;
m.TransactionLink = TransactionLinkProviders.GetTransactionLink(m.Crypto, onChainPaymentData.TransactionId); m.TransactionLink = TransactionLinkProviders.GetTransactionLink(m.PaymentMethodId, onChainPaymentData.TransactionId);
m.Currency = payment.Currency;
return m; return m;
}).Where(c => c != null).ToList(); }).Where(c => c != null).ToList();
} }
@@ -41,7 +42,7 @@
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th class="w-75px">Crypto</th> <th class="w-75px">Payment Method</th>
<th class="w-175px">Destination</th> <th class="w-175px">Destination</th>
<th class="text-nowrap">Payment Proof</th> <th class="text-nowrap">Payment Proof</th>
<th class="text-end">Confirmations</th> <th class="text-end">Confirmations</th>
@@ -52,12 +53,12 @@
@foreach (var payment in payments) @foreach (var payment in payments)
{ {
<tr > <tr >
<td>@payment.Crypto</td> <td>@payment.PaymentMethodId</td>
<td><vc:truncate-center text="@payment.DepositAddress" classes="truncate-center-id" /></td> <td><vc:truncate-center text="@payment.DepositAddress" classes="truncate-center-id" /></td>
<td><vc:truncate-center text="@payment.TransactionId" link="@payment.TransactionLink" classes="truncate-center-id" /></td> <td><vc:truncate-center text="@payment.TransactionId" link="@payment.TransactionLink" classes="truncate-center-id" /></td>
<td class="text-end">@payment.Confirmations</td> <td class="text-end">@payment.Confirmations</td>
<td class="payment-value text-end text-nowrap"> <td class="payment-value text-end text-nowrap">
<span data-sensitive class="text-success">@DisplayFormatter.Currency(payment.Amount, payment.Crypto)</span> <span data-sensitive class="text-success">@DisplayFormatter.Currency(payment.Amount, payment.Currency)</span>
</td> </td>
</tr> </tr>
} }

View File

@@ -242,12 +242,12 @@
@for (var lpi = 0; lpi < linkProviders.Length; lpi++) @for (var lpi = 0; lpi < linkProviders.Length; lpi++)
{ {
var cryptoCode = linkProviders[lpi].Key; var pmi = linkProviders[lpi].Key;
var defaultLink = linkProviders[lpi].Value.BlockExplorerLinkDefault; var defaultLink = linkProviders[lpi].Value.BlockExplorerLinkDefault;
var existingOverride = Model.BlockExplorerLinks?.FirstOrDefault(tuple => tuple.CryptoCode == cryptoCode); var existingOverride = Model.BlockExplorerLinks?.FirstOrDefault(tuple => tuple.PaymentMethodId == pmi);
if (existingOverride is null) if (existingOverride is null)
{ {
existingOverride = new PoliciesSettings.BlockExplorerOverrideItem { CryptoCode = cryptoCode, Link = null }; existingOverride = new PoliciesSettings.BlockExplorerOverrideItem { PaymentMethodId = pmi, Link = null };
Model.BlockExplorerLinks ??= new (); Model.BlockExplorerLinks ??= new ();
Model.BlockExplorerLinks.Add(existingOverride); Model.BlockExplorerLinks.Add(existingOverride);
} }
@@ -255,12 +255,12 @@
var linkValue = existingOverride.Link ?? defaultLink; var linkValue = existingOverride.Link ?? defaultLink;
<div class="form-group" data-default="@defaultLink"> <div class="form-group" data-default="@defaultLink">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3"> <div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
<label asp-for="BlockExplorerLinks[i].Link" class="form-label">@cryptoCode</label> <label asp-for="BlockExplorerLinks[i].Link" class="form-label">@pmi</label>
<button type="button" class="btn btn-link p-0 only-for-js revert-default" title="Revert to default"> <button type="button" class="btn btn-link p-0 only-for-js revert-default" title="Revert to default">
Set to default Set to default
</button> </button>
</div> </div>
<input type="hidden" asp-for="BlockExplorerLinks[i].CryptoCode" value="@cryptoCode" /> <input type="hidden" asp-for="BlockExplorerLinks[i].PaymentMethodId" value="@pmi" />
<input type="text" class="form-control block-explorer-link" asp-for="BlockExplorerLinks[i].Link" value="@linkValue" rel="noreferrer noopener" /> <input type="text" class="form-control block-explorer-link" asp-for="BlockExplorerLinks[i].Link" value="@linkValue" rel="noreferrer noopener" />
</div> </div>
} }