mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
The Big Cleanup: Refactor BTCPay internals (#5809)
This commit is contained in:
parent
69b589a401
commit
6cc1751924
265 changed files with 8289 additions and 7673 deletions
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Data.Common;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<LNURLPayPaymentMethodData>>
|
||||
GetStoreLNURLPayPaymentMethods(string storeId, bool? enabled = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
if (enabled != null)
|
||||
{
|
||||
query.Add(nameof(enabled), enabled);
|
||||
}
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay",
|
||||
query), token);
|
||||
return await HandleResponse<IEnumerable<LNURLPayPaymentMethodData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LNURLPayPaymentMethodData> GetStoreLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}"), token);
|
||||
return await HandleResponse<LNURLPayPaymentMethodData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task RemoveStoreLNURLPayPaymentMethod(string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LNURLPayPaymentMethodData> UpdateStoreLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, LNURLPayPaymentMethodData paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}",
|
||||
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<LNURLPayPaymentMethodData>(response);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<LightningNetworkPaymentMethodData>>
|
||||
GetStoreLightningNetworkPaymentMethods(string storeId, bool? enabled = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
if (enabled != null)
|
||||
{
|
||||
query.Add(nameof(enabled), enabled);
|
||||
}
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork",
|
||||
query), token);
|
||||
return await HandleResponse<IEnumerable<LightningNetworkPaymentMethodData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningNetworkPaymentMethodData> GetStoreLightningNetworkPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}"), token);
|
||||
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task RemoveStoreLightningNetworkPaymentMethod(string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LightningNetworkPaymentMethodData> UpdateStoreLightningNetworkPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, UpdateLightningNetworkPaymentMethodRequest paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}",
|
||||
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,92 +3,47 @@ using System.Net.Http;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
|
||||
bool? enabled = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
if (enabled != null)
|
||||
{
|
||||
query.Add(nameof(enabled), enabled);
|
||||
}
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<OnChainPaymentMethodPreviewResultData>
|
||||
PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string paymentMethodId, string derivationScheme, int offset = 0,
|
||||
int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview",
|
||||
bodyPayload: new UpdatePaymentMethodRequest() { Config = JValue.CreateString(derivationScheme) },
|
||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
||||
}
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain",
|
||||
query), token);
|
||||
return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response);
|
||||
}
|
||||
public virtual async Task<OnChainPaymentMethodPreviewResultData> PreviewStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string paymentMethodId, int offset = 0, int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview",
|
||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainPaymentMethodData> GetStoreOnChainPaymentMethod(string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}"), token);
|
||||
return await HandleResponse<OnChainPaymentMethodData>(response);
|
||||
}
|
||||
public virtual async Task<GenerateOnChainWalletResponse> GenerateOnChainWallet(string storeId,
|
||||
string paymentMethodId, GenerateOnChainWalletRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/generate",
|
||||
bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<GenerateOnChainWalletResponse>(response);
|
||||
}
|
||||
|
||||
public virtual async Task RemoveStoreOnChainPaymentMethod(string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainPaymentMethodData> UpdateStoreOnChainPaymentMethod(string storeId,
|
||||
string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}",
|
||||
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<OnChainPaymentMethodData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainPaymentMethodPreviewResultData>
|
||||
PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0,
|
||||
int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview",
|
||||
bodyPayload: paymentMethod,
|
||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainPaymentMethodPreviewResultData> PreviewStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string cryptoCode, int offset = 0, int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview",
|
||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId,
|
||||
string cryptoCode, GenerateOnChainWalletRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/generate",
|
||||
bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainPaymentMethodDataWithSensitiveData>(response);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
@ -7,21 +8,60 @@ namespace BTCPayServer.Client
|
|||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId,
|
||||
bool? enabled = null,
|
||||
public virtual async Task<GenericPaymentMethodData> UpdateStorePaymentMethod(
|
||||
string storeId,
|
||||
string paymentMethodId,
|
||||
UpdatePaymentMethodRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}", bodyPayload: request, method: HttpMethod.Put),
|
||||
token);
|
||||
return await HandleResponse<GenericPaymentMethodData>(response);
|
||||
}
|
||||
public virtual async Task RemoveStorePaymentMethod(string storeId, string paymentMethodId)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}", method: HttpMethod.Delete),
|
||||
CancellationToken.None);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<GenericPaymentMethodData> GetStorePaymentMethod(string storeId,
|
||||
string paymentMethodId, bool? includeConfig = null, CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
if (enabled != null)
|
||||
if (includeConfig != null)
|
||||
{
|
||||
query.Add(nameof(enabled), enabled);
|
||||
query.Add(nameof(includeConfig), includeConfig);
|
||||
}
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}",
|
||||
query), token);
|
||||
return await HandleResponse<GenericPaymentMethodData>(response);
|
||||
}
|
||||
public virtual async Task<GenericPaymentMethodData[]> GetStorePaymentMethods(string storeId,
|
||||
bool? onlyEnabled = null, bool? includeConfig = null, CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
if (onlyEnabled != null)
|
||||
{
|
||||
query.Add(nameof(onlyEnabled), onlyEnabled);
|
||||
}
|
||||
if (includeConfig != null)
|
||||
{
|
||||
query.Add(nameof(includeConfig), includeConfig);
|
||||
}
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods",
|
||||
query), token);
|
||||
return await HandleResponse<Dictionary<string, GenericPaymentMethodData>>(response);
|
||||
return await HandleResponse<GenericPaymentMethodData[]>(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public class SaneOutpointJsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(OutPoint).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
|
||||
if (reader.TokenType != JsonToken.String)
|
||||
throw new JsonObjectException($"Unexpected json token type, expected is {JsonToken.String} and actual is {reader.TokenType}", reader);
|
||||
try
|
||||
{
|
||||
if (!OutPoint.TryParse((string)reader.Value, out var outpoint))
|
||||
throw new JsonObjectException("Invalid bitcoin object of type OutPoint", reader);
|
||||
return outpoint;
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
}
|
||||
throw new JsonObjectException("Invalid bitcoin object of type OutPoint", reader);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is { })
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
|
@ -21,7 +22,7 @@ namespace BTCPayServer.Client.Models
|
|||
public bool ProceedWithPayjoin { get; set; } = true;
|
||||
public bool ProceedWithBroadcast { get; set; } = true;
|
||||
public bool NoChange { get; set; } = false;
|
||||
[JsonProperty(ItemConverterType = typeof(OutpointJsonConverter))]
|
||||
[JsonProperty(ItemConverterType = typeof(SaneOutpointJsonConverter))]
|
||||
public List<OutPoint> SelectedInputs { get; set; } = null;
|
||||
public List<CreateOnChainTransactionRequestDestination> Destinations { get; set; }
|
||||
[JsonProperty("rbf")]
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Client.Models;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
|
@ -22,4 +25,16 @@ namespace BTCPayServer.Client
|
|||
public bool ImportKeysToRPC { get; set; }
|
||||
public bool SavePrivateKeys { get; set; }
|
||||
}
|
||||
public class GenerateOnChainWalletResponse : GenericPaymentMethodData
|
||||
{
|
||||
public class ConfigData
|
||||
{
|
||||
public string AccountDerivation { get; set; }
|
||||
[JsonExtensionData]
|
||||
IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
||||
[JsonConverter(typeof(MnemonicJsonConverter))]
|
||||
public Mnemonic Mnemonic { get; set; }
|
||||
public new ConfigData Config { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class GenericPaymentMethodData
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public object Data { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public JToken Config { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
}
|
||||
public class UpdatePaymentMethodRequest
|
||||
{
|
||||
public UpdatePaymentMethodRequest()
|
||||
{
|
||||
|
||||
}
|
||||
public bool? Enabled { get; set; }
|
||||
public JToken Config { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,13 +29,12 @@ namespace BTCPayServer.Client.Models
|
|||
public decimal Amount { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal NetworkFee { get; set; }
|
||||
public decimal PaymentMethodFee { get; set; }
|
||||
|
||||
public List<Payment> Payments { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
|
||||
public string CryptoCode { get; set; }
|
||||
public JObject AdditionalData { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
public JToken AdditionalData { get; set; }
|
||||
public string Currency { get; set; }
|
||||
|
||||
public class Payment
|
||||
{
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
public bool UseBech32Scheme { get; set; }
|
||||
|
||||
[JsonProperty("lud12Enabled")]
|
||||
public bool LUD12Enabled { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodBaseData()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LNURLPayPaymentMethodData : LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto code of the payment method
|
||||
/// </summary>
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodData()
|
||||
{
|
||||
}
|
||||
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool lud12Enabled)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
UseBech32Scheme = useBech32Scheme;
|
||||
LUD12Enabled = lud12Enabled;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LightningNetworkPaymentMethodBaseData
|
||||
{
|
||||
|
||||
public string ConnectionString { get; set; }
|
||||
public LightningNetworkPaymentMethodBaseData()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LightningNetworkPaymentMethodData : LightningNetworkPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto code of the payment method
|
||||
/// </summary>
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public LightningNetworkPaymentMethodData()
|
||||
{
|
||||
}
|
||||
|
||||
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled, string paymentMethod)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
ConnectionString = connectionString;
|
||||
PaymentMethod = paymentMethod;
|
||||
}
|
||||
|
||||
public string PaymentMethod { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// The derivation scheme
|
||||
/// </summary>
|
||||
public string DerivationScheme { get; set; }
|
||||
|
||||
public string Label { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
|
||||
public RootedKeyPath AccountKeyPath { get; set; }
|
||||
|
||||
public OnChainPaymentMethodBaseData()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainPaymentMethodDataPreview : OnChainPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Crypto code of the payment method
|
||||
/// </summary>
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public OnChainPaymentMethodDataPreview()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OnChainPaymentMethodDataPreview(string cryptoCode, string derivationScheme, string label, RootedKeyPath accountKeyPath)
|
||||
{
|
||||
Label = label;
|
||||
AccountKeyPath = accountKeyPath;
|
||||
CryptoCode = cryptoCode;
|
||||
DerivationScheme = derivationScheme;
|
||||
}
|
||||
}
|
||||
|
||||
public class OnChainPaymentMethodData : OnChainPaymentMethodDataPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public string PaymentMethod { get; set; }
|
||||
|
||||
public OnChainPaymentMethodData()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled, string label, RootedKeyPath accountKeyPath, string paymentMethod) :
|
||||
base(cryptoCode, derivationScheme, label, accountKeyPath)
|
||||
{
|
||||
Enabled = enabled;
|
||||
PaymentMethod = paymentMethod;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
using BTCPayServer.Client.JsonConverters;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainPaymentMethodDataWithSensitiveData : OnChainPaymentMethodData
|
||||
{
|
||||
public OnChainPaymentMethodDataWithSensitiveData()
|
||||
{
|
||||
}
|
||||
|
||||
public OnChainPaymentMethodDataWithSensitiveData(string cryptoCode, string derivationScheme, bool enabled,
|
||||
string label, RootedKeyPath accountKeyPath, Mnemonic mnemonic, string paymentMethod) : base(cryptoCode, derivationScheme, enabled,
|
||||
label, accountKeyPath, paymentMethod)
|
||||
{
|
||||
Mnemonic = mnemonic;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(MnemonicJsonConverter))]
|
||||
public Mnemonic Mnemonic { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
|
@ -12,7 +13,7 @@ namespace BTCPayServer.Client.Models
|
|||
public string Comment { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
[JsonConverter(typeof(OutpointJsonConverter))]
|
||||
[JsonConverter(typeof(SaneOutpointJsonConverter))]
|
||||
public OutPoint Outpoint { get; set; }
|
||||
public string Link { get; set; }
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class UpdateLightningNetworkPaymentMethodRequest : LightningNetworkPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public UpdateLightningNetworkPaymentMethodRequest()
|
||||
{
|
||||
}
|
||||
|
||||
public UpdateLightningNetworkPaymentMethodRequest(string connectionString, bool enabled)
|
||||
{
|
||||
Enabled = enabled;
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class UpdateOnChainPaymentMethodRequest : OnChainPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public UpdateOnChainPaymentMethodRequest()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public UpdateOnChainPaymentMethodRequest(bool enabled, string derivationScheme, string label, RootedKeyPath accountKeyPath)
|
||||
{
|
||||
Enabled = enabled;
|
||||
Label = label;
|
||||
AccountKeyPath = accountKeyPath;
|
||||
DerivationScheme = derivationScheme;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,7 +91,7 @@ namespace BTCPayServer.Client.Models
|
|||
}
|
||||
|
||||
public bool AfterExpiration { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public string PaymentMethodId { get; set; }
|
||||
public InvoicePaymentMethodDataModel.Payment Payment { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
@ -130,6 +130,11 @@ namespace BTCPayServer
|
|||
{
|
||||
return transactionInformationSet;
|
||||
}
|
||||
|
||||
public string GetTrackedDestination(Script scriptPubKey)
|
||||
{
|
||||
return scriptPubKey.Hash.ToString() + "#" + CryptoCode.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BTCPayNetworkBase
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace BTCPayServer.Data
|
|||
public override ApplicationDbContext CreateContext()
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance);
|
||||
ConfigureBuilder(builder);
|
||||
return new ApplicationDbContext(builder.Options);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.1" />
|
||||
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.23" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||
|
|
|
@ -6,11 +6,6 @@ namespace BTCPayServer.Data
|
|||
{
|
||||
public class AddressInvoiceData
|
||||
{
|
||||
/// <summary>
|
||||
/// Some crypto currencies share same address prefix
|
||||
/// For not having exceptions thrown by two address on different network, we suffix by "#CRYPTOCODE"
|
||||
/// </summary>
|
||||
[Obsolete("Use GetHash instead")]
|
||||
public string Address { get; set; }
|
||||
public InvoiceData InvoiceData { get; set; }
|
||||
public string InvoiceDataId { get; set; }
|
||||
|
|
368
BTCPayServer.Data/Data/InvoiceData.Migration.cs
Normal file
368
BTCPayServer.Data/Data/InvoiceData.Migration.cs
Normal file
|
@ -0,0 +1,368 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.IO.Compression;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using BTCPayServer.Migrations;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public partial class InvoiceData
|
||||
{
|
||||
/// <summary>
|
||||
/// We have a migration running in the background that will migrate the data from the old blob to the new blob
|
||||
/// Meanwhile, we need to make sure that invoices which haven't been migrated yet are migrated on the fly.
|
||||
/// </summary>
|
||||
public class MigrationInterceptor : IMaterializationInterceptor
|
||||
{
|
||||
public static readonly MigrationInterceptor Instance = new MigrationInterceptor();
|
||||
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
|
||||
{
|
||||
if (entity is InvoiceData invoiceData)
|
||||
{
|
||||
invoiceData.Migrate();
|
||||
}
|
||||
else if (entity is PaymentData paymentData)
|
||||
{
|
||||
paymentData.Migrate();
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
static HashSet<string> superflousProperties = new HashSet<string>()
|
||||
{
|
||||
"availableAddressHashes",
|
||||
"events",
|
||||
"refunds",
|
||||
"paidAmount",
|
||||
"historicalAddresses",
|
||||
"refundable",
|
||||
"status",
|
||||
"exceptionStatus",
|
||||
"storeId",
|
||||
"id",
|
||||
"txFee",
|
||||
"refundMail",
|
||||
"rate",
|
||||
"depositAddress",
|
||||
"currency",
|
||||
"price",
|
||||
"payments",
|
||||
"orderId",
|
||||
"buyerInformation",
|
||||
"productInformation",
|
||||
"derivationStrategy",
|
||||
"archived",
|
||||
"isUnderPaid",
|
||||
"requiresRefundEmail"
|
||||
};
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public void Migrate()
|
||||
{
|
||||
if (Currency is not null)
|
||||
return;
|
||||
if (Blob is not null)
|
||||
{
|
||||
Blob2 = MigrationExtensions.Unzip(Blob);
|
||||
Blob = null;
|
||||
}
|
||||
var blob = JObject.Parse(Blob2);
|
||||
if (blob["cryptoData"]?["BTC"] is not null)
|
||||
{
|
||||
blob.Move(["rate"], ["cryptoData", "BTC", "rate"]);
|
||||
blob.Move(["txFee"], ["cryptoData", "BTC", "txFee"]);
|
||||
}
|
||||
blob.Move(["customerEmail"], ["metadata", "buyerEmail"]);
|
||||
foreach (var prop in (blob["cryptoData"] as JObject)?.Properties()?.ToList() ?? [])
|
||||
{
|
||||
// We should only change data for onchain
|
||||
if (prop.Name.Contains('_', StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (prop.Value is JObject pm)
|
||||
{
|
||||
pm.Remove("depositAddress");
|
||||
pm.Remove("feeRate");
|
||||
pm.Remove("txFee");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (prop.Value is JObject o)
|
||||
{
|
||||
o.ConvertNumberToString("rate");
|
||||
if (o["paymentMethod"] is JObject pm)
|
||||
{
|
||||
if (pm["networkFeeRate"] is null)
|
||||
pm["networkFeeRate"] = o["feeRate"] ?? 0.0m;
|
||||
if (pm["networkFeeMode"] is JValue { Type: JTokenType.Integer, Value: 0 or 0L })
|
||||
pm.Remove("networkFeeMode");
|
||||
if (pm["networkFeeMode"] is JValue { Type: JTokenType.Integer, Value: 2 or 2L })
|
||||
pm["networkFeeRate"] = 0.0m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var metadata = blob.Property("metadata")?.Value as JObject;
|
||||
if (metadata is null)
|
||||
{
|
||||
metadata = new JObject();
|
||||
blob.Add("metadata", metadata);
|
||||
}
|
||||
foreach (var prop in (blob["buyerInformation"] as JObject)?.Properties()?.ToList() ?? [])
|
||||
{
|
||||
if (prop.Value?.Value<string>() is not null)
|
||||
blob.Move(["buyerInformation", prop.Name], ["metadata", prop.Name]);
|
||||
}
|
||||
foreach (var prop in (blob["productInformation"] as JObject)?.Properties()?.ToList() ?? [])
|
||||
{
|
||||
if (prop.Name is "price" or "currency")
|
||||
blob.Move(["productInformation", prop.Name], [prop.Name]);
|
||||
else if (prop.Value?.Value<string>() is not null)
|
||||
blob.Move(["productInformation", prop.Name], ["metadata", prop.Name]);
|
||||
}
|
||||
blob.Move(["orderId"], ["metadata", "orderId"]);
|
||||
foreach (string prop in new string[] { "posData", "checkoutType", "defaultLanguage", "notificationEmail", "notificationURL", "storeSupportUrl", "redirectURL" })
|
||||
{
|
||||
blob.RemoveIfNull(prop);
|
||||
}
|
||||
blob.RemoveIfValue<bool>("fullNotifications", false);
|
||||
if (blob["receiptOptions"] is JObject receiptOptions)
|
||||
{
|
||||
foreach (string prop in new string[] { "showQR", "enabled", "showPayments" })
|
||||
{
|
||||
receiptOptions.RemoveIfNull(prop);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (blob.Property("paymentTolerance") is JProperty { Value: { Type: JTokenType.Float } pv } prop)
|
||||
{
|
||||
if (pv.Value<decimal>() == 0.0m)
|
||||
prop.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
var posData = blob.Move(["posData"], ["metadata", "posData"]);
|
||||
if (posData is not null && posData.Value?.Type is JTokenType.String)
|
||||
{
|
||||
try
|
||||
{
|
||||
posData.Value = JObject.Parse(posData.Value<string>());
|
||||
}
|
||||
catch
|
||||
{
|
||||
posData.Remove();
|
||||
}
|
||||
}
|
||||
if (posData?.Type is JTokenType.Null)
|
||||
posData.Remove();
|
||||
|
||||
if (blob["derivationStrategies"] is JValue { Type: JTokenType.String } v)
|
||||
blob["derivationStrategies"] = JObject.Parse(v.Value<string>());
|
||||
if (blob["derivationStrategies"] is JObject derivations)
|
||||
{
|
||||
foreach (var prop in derivations.Properties().ToList())
|
||||
{
|
||||
// We should only change data for onchain
|
||||
if (prop.Name.Contains('_', StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (prop.Value is JValue
|
||||
{
|
||||
Type: JTokenType.String,
|
||||
Value: String { Length: > 0 } val
|
||||
})
|
||||
{
|
||||
if (val[0] == '{')
|
||||
derivations[prop.Name] = JObject.Parse(val);
|
||||
else
|
||||
{
|
||||
if (val.Contains('-', StringComparison.OrdinalIgnoreCase))
|
||||
derivations[prop.Name] = new JObject() { ["accountDerivation"] = val };
|
||||
else
|
||||
derivations[prop.Name] = null;
|
||||
}
|
||||
}
|
||||
if (prop.Value is JObject derivation)
|
||||
{
|
||||
derivations[prop.Name] = derivation["accountDerivation"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blob["derivationStrategies"] is null && blob["derivationStrategy"] is not null)
|
||||
{
|
||||
// If it's NBX derivation strategy, keep it. Else just give up, it might be Electrum format and we shouldn't support
|
||||
// that anymore in the backend for long...
|
||||
if (blob["derivationStrategy"]?.Value<string>().Contains('-', StringComparison.OrdinalIgnoreCase) is true)
|
||||
blob.Move(["derivationStrategy"], ["derivationStrategies", "BTC"]);
|
||||
else
|
||||
{
|
||||
blob.Remove("derivationStrategy");
|
||||
blob.Add("derivationStrategies", new JObject() { ["BTC"] = null });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (blob["type"]?.Value<string>() is "Standard")
|
||||
blob.Remove("type");
|
||||
foreach (var prop in new string[] { "extendedNotifications", "lazyPaymentMethods", "lazyPaymentMethods", "redirectAutomatically" })
|
||||
{
|
||||
if (blob[prop]?.Value<bool>() is false)
|
||||
blob.Remove(prop);
|
||||
}
|
||||
|
||||
blob.ConvertNumberToString("price");
|
||||
Currency = blob["currency"].Value<string>();
|
||||
var isTopup = blob["type"]?.Value<string>() is "TopUp";
|
||||
var amount = decimal.Parse(blob["price"].Value<string>(), CultureInfo.InvariantCulture);
|
||||
Amount = isTopup && amount == 0 ? null : decimal.Parse(blob["price"].Value<string>(), CultureInfo.InvariantCulture);
|
||||
CustomerEmail = null;
|
||||
foreach (var prop in superflousProperties)
|
||||
blob.Property(prop)?.Remove();
|
||||
if (blob["speedPolicy"] is JValue { Type: JTokenType.Integer, Value: 0 or 0L })
|
||||
blob.Remove("speedPolicy");
|
||||
blob.TryAdd("internalTags", new JArray());
|
||||
blob.TryAdd("receiptOptions", new JObject());
|
||||
|
||||
foreach (var prop in ((JObject)blob["cryptoData"]).Properties())
|
||||
{
|
||||
if (prop.Name.EndsWith("_LightningLike", StringComparison.OrdinalIgnoreCase) ||
|
||||
prop.Name.EndsWith("_LNURLPAY", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (prop.Value["paymentMethod"]?["PaymentHash"] is JObject)
|
||||
prop.Value["paymentMethod"]["PaymentHash"] = JValue.CreateNull();
|
||||
if (prop.Value["paymentMethod"]?["Preimage"] is JObject)
|
||||
prop.Value["paymentMethod"]["Preimage"] = JValue.CreateNull();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var prop in ((JObject)blob["cryptoData"]).Properties())
|
||||
{
|
||||
var crypto = prop.Name.Split(['_', '-']).First();
|
||||
if (blob.Move(["cryptoData", prop.Name, "rate"], ["rates", crypto]) is not null)
|
||||
((JObject)blob["rates"]).ConvertNumberToString(crypto);
|
||||
}
|
||||
blob.Move(["cryptoData"], ["prompts"]);
|
||||
var prompts = ((JObject)blob["prompts"]);
|
||||
foreach (var prop in prompts.Properties().ToList())
|
||||
{
|
||||
((JObject)blob["prompts"]).RenameProperty(prop.Name, MigrationExtensions.MigratePaymentMethodId(prop.Name));
|
||||
}
|
||||
blob["derivationStrategies"] = blob["derivationStrategies"] ?? new JObject();
|
||||
foreach (var prop in ((JObject)blob["derivationStrategies"]).Properties().ToList())
|
||||
{
|
||||
((JObject)blob["derivationStrategies"]).RenameProperty(prop.Name, MigrationExtensions.MigratePaymentMethodId(prop.Name));
|
||||
}
|
||||
|
||||
foreach (var prop in prompts.Properties())
|
||||
{
|
||||
var prompt = prop.Value as JObject;
|
||||
if (prompt is null)
|
||||
continue;
|
||||
prompt["currency"] = prop.Name.Split('-').First();
|
||||
prompt.RemoveIfNull("depositAddress");
|
||||
prompt.RemoveIfNull("txFee");
|
||||
prompt.RemoveIfNull("feeRate");
|
||||
|
||||
prompt.RenameProperty("depositAddress", "destination");
|
||||
prompt.RenameProperty("txFee", "paymentMethodFee");
|
||||
|
||||
var divisibility = MigrationExtensions.GetDivisibility(prop.Name);
|
||||
prompt.Add("divisibility", divisibility);
|
||||
if (prompt["paymentMethodFee"] is { } paymentMethodFee)
|
||||
{
|
||||
prompt["paymentMethodFee"] = ((decimal)paymentMethodFee.Value<long>() / (decimal)Math.Pow(10, divisibility)).ToString(CultureInfo.InvariantCulture);
|
||||
prompt.RemoveIfValue<string>("paymentMethodFee", "0");
|
||||
}
|
||||
prompt.Move(["paymentMethod"], ["details"]);
|
||||
prompt.Move(["feeRate"], ["details", "recommendedFeeRate"]);
|
||||
prompt.Move(["details", "networkFeeRate"], ["details", "paymentMethodFeeRate"]);
|
||||
prompt.Move(["details", "networkFeeMode"], ["details", "feeMode"]);
|
||||
if ((prompt["details"]?["Activated"])?.Value<bool>() is bool activated)
|
||||
{
|
||||
((JObject)prompt["details"]).Remove("Activated");
|
||||
prompt["inactive"] = !activated;
|
||||
prompt.RemoveIfValue<bool>("inactive", false);
|
||||
}
|
||||
if ((prompt["details"]?["activated"])?.Value<bool>() is bool activated2)
|
||||
{
|
||||
((JObject)prompt["details"]).Remove("activated");
|
||||
prompt["inactive"] = !activated2;
|
||||
prompt.RemoveIfValue<bool>("inactive", false);
|
||||
}
|
||||
var details = prompt["details"] as JObject ?? new JObject();
|
||||
details.RemoveIfValue<bool>("payjoinEnabled", false);
|
||||
details.RemoveIfNull("feeMode");
|
||||
if (details["feeMode"] is not null)
|
||||
{
|
||||
details["feeMode"] = details["feeMode"].Value<int>() switch
|
||||
{
|
||||
1 => "Always",
|
||||
2 => "Never",
|
||||
_ => null
|
||||
};
|
||||
details.RemoveIfNull("feeMode");
|
||||
}
|
||||
|
||||
details.RemoveIfNull("BOLT11");
|
||||
details.RemoveIfNull("address");
|
||||
details.RemoveIfNull("Address");
|
||||
prompt.Move(["details", "BOLT11"], ["destination"]);
|
||||
prompt.Move(["details", "address"], ["destination"]);
|
||||
prompt.Move(["details", "Address"], ["destination"]);
|
||||
prompt.RenameProperty("Address", "destination");
|
||||
prompt.RenameProperty("BOLT11", "destination");
|
||||
|
||||
details.Remove("LightningSupportedPaymentMethod");
|
||||
foreach (var o in detailsRemoveDefault)
|
||||
details.RemoveIfNull(o);
|
||||
details.RemoveIfValue<decimal>("recommendedFeeRate", 0.0m);
|
||||
details.RemoveIfValue<decimal>("paymentMethodFeeRate", 0.0m);
|
||||
if (prop.Name.EndsWith("-CHAIN"))
|
||||
blob.Move(["derivationStrategies", prop.Name], ["prompts", prop.Name, "details", "accountDerivation"]);
|
||||
|
||||
var camel = new CamelCaseNamingStrategy();
|
||||
foreach (var p in details.Properties().ToList())
|
||||
{
|
||||
var camelName = camel.GetPropertyName(p.Name, false);
|
||||
if (camelName != p.Name)
|
||||
details.RenameProperty(p.Name, camelName);
|
||||
}
|
||||
}
|
||||
|
||||
if (blob["defaultPaymentMethod"] is not null)
|
||||
blob["defaultPaymentMethod"] = MigrationExtensions.MigratePaymentMethodId(blob["defaultPaymentMethod"].Value<string>());
|
||||
blob.Remove("derivationStrategies");
|
||||
|
||||
blob["version"] = 3;
|
||||
Blob2 = blob.ToString(Formatting.None);
|
||||
}
|
||||
static string[] detailsRemoveDefault =
|
||||
[
|
||||
"paymentMethodFeeRate",
|
||||
"keyPath",
|
||||
"BOLT11",
|
||||
"NodeInfo",
|
||||
"Preimage",
|
||||
"InvoiceId",
|
||||
"PaymentHash",
|
||||
"ProvidedComment",
|
||||
"GeneratedBoltAmount",
|
||||
"ConsumedLightningAddress",
|
||||
"PayRequest"
|
||||
];
|
||||
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class InvoiceData : IHasBlobUntyped
|
||||
public partial class InvoiceData : IHasBlobUntyped
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Currency { get; set; }
|
||||
public decimal? Amount { get; set; }
|
||||
public string StoreDataId { get; set; }
|
||||
public StoreData StoreData { get; set; }
|
||||
|
||||
|
@ -25,6 +25,7 @@ namespace BTCPayServer.Data
|
|||
public string OrderId { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string ExceptionStatus { get; set; }
|
||||
[Obsolete("Unused")]
|
||||
public string CustomerEmail { get; set; }
|
||||
public List<AddressInvoiceData> AddressInvoices { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
@ -43,12 +44,14 @@ namespace BTCPayServer.Data
|
|||
builder.Entity<InvoiceData>().HasIndex(o => o.StoreDataId);
|
||||
builder.Entity<InvoiceData>().HasIndex(o => o.OrderId);
|
||||
builder.Entity<InvoiceData>().HasIndex(o => o.Created);
|
||||
|
||||
if (databaseFacade.IsNpgsql())
|
||||
{
|
||||
builder.Entity<InvoiceData>()
|
||||
.Property(o => o.Blob2)
|
||||
.HasColumnType("JSONB");
|
||||
builder.Entity<InvoiceData>()
|
||||
.Property(o => o.Amount)
|
||||
.HasColumnType("NUMERIC");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
151
BTCPayServer.Data/Data/MigrationExtensions.cs
Normal file
151
BTCPayServer.Data/Data/MigrationExtensions.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public static class MigrationExtensions
|
||||
{
|
||||
public static JProperty? Move(this JObject blob, string[] pathFrom, string[] pathTo)
|
||||
{
|
||||
var from = GetProperty(blob, pathFrom, false);
|
||||
if (from is null)
|
||||
return null;
|
||||
var to = GetProperty(blob, pathTo, true);
|
||||
to!.Value = from.Value;
|
||||
from.Remove();
|
||||
return to;
|
||||
}
|
||||
|
||||
public static void RenameProperty(this JObject o, string oldName, string newName)
|
||||
{
|
||||
var p = o.Property(oldName);
|
||||
if (p is null)
|
||||
return;
|
||||
RenameProperty(ref p, newName);
|
||||
}
|
||||
public static void RenameProperty(ref JProperty ls, string newName)
|
||||
{
|
||||
if (ls.Name != newName)
|
||||
{
|
||||
var parent = ls.Parent;
|
||||
ls.Remove();
|
||||
ls = new JProperty(newName, ls.Value);
|
||||
parent!.Add(ls);
|
||||
}
|
||||
}
|
||||
|
||||
public static JProperty? GetProperty(this JObject blob, string[] pathFrom, bool createIfNotExists)
|
||||
{
|
||||
var current = blob;
|
||||
for (int i = 0; i < pathFrom.Length - 1; i++)
|
||||
{
|
||||
if (current.TryGetValue(pathFrom[i], out var value) && value is JObject jObject)
|
||||
{
|
||||
current = jObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!createIfNotExists)
|
||||
return null;
|
||||
JProperty? prop = null;
|
||||
for (int ii = i; ii < pathFrom.Length; ii++)
|
||||
{
|
||||
var newProp = new JProperty(pathFrom[ii], new JObject());
|
||||
if (prop is null)
|
||||
current.Add(newProp);
|
||||
else
|
||||
prop.Value = new JObject(newProp);
|
||||
prop = newProp;
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
var result = current.Property(pathFrom[pathFrom.Length - 1]);
|
||||
if (result is null && createIfNotExists)
|
||||
{
|
||||
result = new JProperty(pathFrom[pathFrom.Length - 1], null as object);
|
||||
current.Add(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public static CamelCaseNamingStrategy Camel = new CamelCaseNamingStrategy();
|
||||
public static void RemoveIfNull(this JObject blob, string propName)
|
||||
{
|
||||
if (blob.Property(propName)?.Value.Type is JTokenType.Null)
|
||||
blob.Remove(propName);
|
||||
}
|
||||
public static void RemoveIfValue<T>(this JObject conf, string propName, T v)
|
||||
{
|
||||
var p = conf.Property(propName);
|
||||
if (p is null)
|
||||
return;
|
||||
if (p.Value is JValue { Type: JTokenType.Null })
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(default, v))
|
||||
p.Remove();
|
||||
}
|
||||
else if (p.Value is JValue jv)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(jv.Value<T>(), v))
|
||||
{
|
||||
p.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ConvertNumberToString(this JObject o, string prop)
|
||||
{
|
||||
if (o[prop]?.Type is JTokenType.Float)
|
||||
o[prop] = o[prop]!.Value<decimal>().ToString(CultureInfo.InvariantCulture);
|
||||
if (o[prop]?.Type is JTokenType.Integer)
|
||||
o[prop] = o[prop]!.Value<long>().ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
public static string Unzip(byte[] bytes)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream(bytes);
|
||||
using GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress);
|
||||
StreamReader reader = new StreamReader(gzip, Encoding.UTF8);
|
||||
var unzipped = reader.ReadToEnd();
|
||||
return unzipped;
|
||||
}
|
||||
|
||||
public static int GetDivisibility(string paymentMethodId)
|
||||
{
|
||||
var splitted = paymentMethodId.Split('-');
|
||||
return (CryptoCode: splitted[0], Type: splitted[1]) switch
|
||||
{
|
||||
{ Type: "LN" } or { Type: "LNURL" } => 11,
|
||||
{ Type: "CHAIN", CryptoCode: var code } when code == "XMR" => 12,
|
||||
{ Type: "CHAIN" } => 8,
|
||||
_ => 8
|
||||
};
|
||||
}
|
||||
|
||||
public static string MigratePaymentMethodId(string paymentMethodId)
|
||||
{
|
||||
var splitted = paymentMethodId.Split(new[] { '_', '-' });
|
||||
if (splitted is [var cryptoCode, var paymentType])
|
||||
{
|
||||
return paymentType switch
|
||||
{
|
||||
"BTCLike" => $"{cryptoCode}-CHAIN",
|
||||
"LightningLike" or "LightningNetwork" => $"{cryptoCode}-LN",
|
||||
"LNURLPAY" => $"{cryptoCode}-LNURL",
|
||||
_ => throw new NotSupportedException("Unknown payment type " + paymentType)
|
||||
};
|
||||
}
|
||||
if (splitted.Length == 1)
|
||||
return $"{splitted[0]}-CHAIN";
|
||||
throw new NotSupportedException("Unknown payment id " + paymentMethodId);
|
||||
}
|
||||
}
|
||||
}
|
179
BTCPayServer.Data/Data/PaymentData.Migration.cs
Normal file
179
BTCPayServer.Data/Data/PaymentData.Migration.cs
Normal file
|
@ -0,0 +1,179 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Migrations;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Altcoins;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public partial class PaymentData
|
||||
{
|
||||
public void Migrate()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (Currency is not null)
|
||||
return;
|
||||
if (Blob is not null)
|
||||
{
|
||||
Blob2 = MigrationExtensions.Unzip(Blob);
|
||||
Blob = null;
|
||||
}
|
||||
var blob = JObject.Parse(Blob2);
|
||||
if (blob["cryptoPaymentDataType"] is null)
|
||||
blob["cryptoPaymentDataType"] = "BTCLike";
|
||||
if (blob["cryptoCode"] is null)
|
||||
blob["cryptoCode"] = "BTC";
|
||||
|
||||
if (blob["receivedTime"] is null)
|
||||
blob.Move(["receivedTimeMs"], ["receivedTime"]);
|
||||
else
|
||||
{
|
||||
// Convert number of seconds to number of milliseconds
|
||||
var timeSeconds = (ulong)(long)blob["receivedTime"].Value<long>();
|
||||
var date = NBitcoin.Utils.UnixTimeToDateTime(timeSeconds);
|
||||
blob["receivedTime"] = DateTimeToMilliUnixTime(date.UtcDateTime);
|
||||
}
|
||||
|
||||
var cryptoCode = blob["cryptoCode"].Value<string>();
|
||||
Type = cryptoCode + "_" + blob["cryptoPaymentDataType"].Value<string>();
|
||||
Type = MigrationExtensions.MigratePaymentMethodId(Type);
|
||||
var divisibility = MigrationExtensions.GetDivisibility(Type);
|
||||
Currency = blob["cryptoCode"].Value<string>();
|
||||
blob.Remove("cryptoCode");
|
||||
blob.Remove("cryptoPaymentDataType");
|
||||
|
||||
JObject cryptoData;
|
||||
if (blob["cryptoPaymentData"] is null)
|
||||
{
|
||||
cryptoData = new JObject();
|
||||
blob["cryptoPaymentData"] = cryptoData;
|
||||
cryptoData["RBF"] = true;
|
||||
cryptoData["confirmationCount"] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
cryptoData = JObject.Parse(blob["cryptoPaymentData"].Value<string>());
|
||||
foreach (var prop in cryptoData.Properties().ToList())
|
||||
{
|
||||
if (prop.Name is "rbf")
|
||||
cryptoData.RenameProperty("rbf", "RBF");
|
||||
else if (prop.Name is "bolT11")
|
||||
cryptoData.RenameProperty("bolT11", "BOLT11");
|
||||
else
|
||||
cryptoData.RenameProperty(prop.Name, MigrationExtensions.Camel.GetPropertyName(prop.Name, false));
|
||||
}
|
||||
}
|
||||
blob.Remove("cryptoPaymentData");
|
||||
cryptoData["outpoint"] = blob["outpoint"];
|
||||
if (blob["output"] is not (null or { Type: JTokenType.Null }))
|
||||
{
|
||||
// Old versions didn't track addresses, so we take it from output.
|
||||
// We don't know the network for sure but better having something than nothing in destination.
|
||||
// If signet/testnet crash we don't really care anyway.
|
||||
// Also, only LTC was supported at this time.
|
||||
Network network = (cryptoCode switch { "LTC" => (INetworkSet)Litecoin.Instance, _ => Bitcoin.Instance }).Mainnet;
|
||||
var txout = network.Consensus.ConsensusFactory.CreateTxOut();
|
||||
txout.ReadWrite(Encoders.Hex.DecodeData(blob["output"].Value<string>()), network);
|
||||
cryptoData["value"] = txout.Value.Satoshi;
|
||||
blob["destination"] = txout.ScriptPubKey.GetDestinationAddress(network)?.ToString();
|
||||
}
|
||||
blob.Remove("output");
|
||||
blob.Remove("outpoint");
|
||||
// Convert from sats to btc
|
||||
if (cryptoData["value"] is not (null or { Type: JTokenType.Null }))
|
||||
{
|
||||
var v = cryptoData["value"].Value<long>();
|
||||
Amount = (decimal)v / (decimal)Money.COIN;
|
||||
cryptoData.Remove("value");
|
||||
|
||||
blob["paymentMethodFee"] = blob["networkFee"];
|
||||
blob.RemoveIfValue<decimal>("paymentMethodFee", 0.0m);
|
||||
blob.ConvertNumberToString("paymentMethodFee");
|
||||
blob.Remove("networkFee");
|
||||
blob.RemoveIfNull("paymentMethodFee");
|
||||
}
|
||||
// Convert from millisats to btc
|
||||
else if (cryptoData["amount"] is not (null or { Type: JTokenType.Null }))
|
||||
{
|
||||
var v = cryptoData["amount"].Value<long>();
|
||||
Amount = (decimal)v / (decimal)Math.Pow(10.0, divisibility);
|
||||
cryptoData.Remove("amount");
|
||||
}
|
||||
if (cryptoData["address"] is not (null or { Type: JTokenType.Null }))
|
||||
{
|
||||
blob["destination"] = cryptoData["address"];
|
||||
cryptoData.Remove("address");
|
||||
}
|
||||
if (cryptoData["BOLT11"] is not (null or { Type: JTokenType.Null }))
|
||||
{
|
||||
blob["destination"] = cryptoData["BOLT11"];
|
||||
cryptoData.Remove("BOLT11");
|
||||
}
|
||||
if (cryptoData["outpoint"] is not (null or { Type: JTokenType.Null }))
|
||||
{
|
||||
// Convert to format txid-n
|
||||
cryptoData["outpoint"] = OutPoint.Parse(cryptoData["outpoint"].Value<string>()).ToString();
|
||||
}
|
||||
if (Accounted is false)
|
||||
Status = PaymentStatus.Unaccounted;
|
||||
else if (cryptoData["confirmationCount"] is { Type: JTokenType.Integer })
|
||||
{
|
||||
var confirmationCount = cryptoData["confirmationCount"].Value<int>();
|
||||
// Technically, we should use the invoice's speed policy, however it's not on our
|
||||
// scope and is good enough for majority of cases.
|
||||
Status = confirmationCount > 0 ? PaymentStatus.Settled : PaymentStatus.Processing;
|
||||
if (cryptoData["LockTime"] is { Type: JTokenType.Integer })
|
||||
{
|
||||
var lockTime = cryptoData["LockTime"].Value<int>();
|
||||
if (confirmationCount < lockTime)
|
||||
Status = PaymentStatus.Processing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Status = PaymentStatus.Settled;
|
||||
}
|
||||
Created = MilliUnixTimeToDateTime(blob["receivedTime"].Value<long>());
|
||||
cryptoData.RemoveIfValue<bool>("rbf", false);
|
||||
cryptoData.Remove("legacy");
|
||||
cryptoData.Remove("networkFee");
|
||||
cryptoData.Remove("paymentType");
|
||||
cryptoData.RemoveIfNull("outpoint");
|
||||
cryptoData.RemoveIfValue<bool>("RBF", false);
|
||||
|
||||
blob.Remove("receivedTime");
|
||||
blob.Remove("accounted");
|
||||
blob.Remove("networkFee");
|
||||
blob["details"] = cryptoData;
|
||||
blob["divisibility"] = divisibility;
|
||||
blob["version"] = 2;
|
||||
Blob2 = blob.ToString(Formatting.None);
|
||||
Accounted = null;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
static readonly DateTimeOffset unixRef = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
public static long DateTimeToMilliUnixTime(in DateTime time)
|
||||
{
|
||||
var date = ((DateTimeOffset)time).ToUniversalTime();
|
||||
long v = (long)(date - unixRef).TotalMilliseconds;
|
||||
if (v < 0)
|
||||
throw new FormatException("Invalid datetime (less than 1/1/1970)");
|
||||
return v;
|
||||
}
|
||||
public static DateTimeOffset MilliUnixTimeToDateTime(long value)
|
||||
{
|
||||
var v = value;
|
||||
if (v < 0)
|
||||
throw new FormatException("Invalid datetime (less than 1/1/1970)");
|
||||
return unixRef + TimeSpan.FromMilliseconds(v);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,17 +4,32 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
|||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class PaymentData : IHasBlobUntyped
|
||||
public enum PaymentStatus
|
||||
{
|
||||
Processing,
|
||||
Settled,
|
||||
Unaccounted
|
||||
}
|
||||
public partial class PaymentData : IHasBlobUntyped
|
||||
{
|
||||
/// <summary>
|
||||
/// The date of creation of the payment
|
||||
/// Note that while it is a nullable field, our migration
|
||||
/// process ensure it is populated.
|
||||
/// </summary>
|
||||
public DateTimeOffset? Created { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string InvoiceDataId { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public decimal? Amount { get; set; }
|
||||
public InvoiceData InvoiceData { get; set; }
|
||||
[Obsolete("Use Blob2 instead")]
|
||||
public byte[] Blob { get; set; }
|
||||
public string Blob2 { get; set; }
|
||||
public string Type { get; set; }
|
||||
public bool Accounted { get; set; }
|
||||
|
||||
[Obsolete("Use Status instead")]
|
||||
public bool? Accounted { get; set; }
|
||||
public PaymentStatus? Status { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
|
@ -23,11 +38,17 @@ namespace BTCPayServer.Data
|
|||
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Entity<PaymentData>()
|
||||
.HasIndex(o => o.InvoiceDataId);
|
||||
builder.Entity<PaymentData>()
|
||||
.Property(o => o.Status)
|
||||
.HasConversion<string>();
|
||||
if (databaseFacade.IsNpgsql())
|
||||
{
|
||||
builder.Entity<PaymentData>()
|
||||
.Property(o => o.Blob2)
|
||||
.HasColumnType("JSONB");
|
||||
builder.Entity<PaymentData>()
|
||||
.Property(o => o.Amount)
|
||||
.HasColumnType("NUMERIC");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using BTCPayServer.Client.Models;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240304003640_addinvoicecolumns")]
|
||||
public partial class addinvoicecolumns : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "Amount",
|
||||
table: "Invoices",
|
||||
type: migrationBuilder.IsNpgsql() ? "NUMERIC" : "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Currency",
|
||||
table: "Invoices",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Amount",
|
||||
table: "Invoices");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Currency",
|
||||
table: "Invoices");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240317024757_payments_refactor")]
|
||||
public partial class payments_refactor : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "Amount",
|
||||
table: "Payments",
|
||||
type: migrationBuilder.IsNpgsql() ? "NUMERIC" : "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "Created",
|
||||
table: "Payments",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Currency",
|
||||
table: "Payments",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Status",
|
||||
table: "Payments",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
if (migrationBuilder.IsNpgsql())
|
||||
{
|
||||
migrationBuilder.AlterColumn<bool?>(
|
||||
name: "Accounted",
|
||||
table: "Payments",
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Amount",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Created",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Currency",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Status",
|
||||
table: "Payments");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -247,6 +247,9 @@ namespace BTCPayServer.Migrations
|
|||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal?>("Amount")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Archived")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -259,6 +262,9 @@ namespace BTCPayServer.Migrations
|
|||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CustomerEmail")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
@ -505,15 +511,27 @@ namespace BTCPayServer.Migrations
|
|||
b.Property<bool>("Accounted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal?>("Amount")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Blob2")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InvoiceDataId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ using BTCPayServer.Plugins.PointOfSale;
|
|||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
|
@ -172,12 +173,13 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// Now let's check that no data has been lost in the process
|
||||
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
|
||||
var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
.OfType<DerivationSchemeSettings>().First(o => o.PaymentId.IsBTCOnChain);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
FastTests.GetParsers().TryParseWalletFile(content, onchainBTC.Network, out var expected, out var error);
|
||||
Assert.Equal(expected.ToJson(), onchainBTC.ToJson());
|
||||
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var onchainBTC = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||
var network = handlers.GetBitcoinHandler("BTC").Network;
|
||||
FastTests.GetParsers().TryParseWalletFile(content, network, out var expected, out var error);
|
||||
var handler = handlers[pmi];
|
||||
Assert.Equal(JToken.FromObject(expected, handler.Serializer), JToken.FromObject(onchainBTC, handler.Serializer));
|
||||
Assert.Null(error);
|
||||
|
||||
// Let's check that the root hdkey and account key path are taken into account when making a PSBT
|
||||
|
@ -302,6 +304,7 @@ namespace BTCPayServer.Tests
|
|||
var cashCow = tester.LTCExplorerNode;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||
var firstPayment = Money.Coins(0.1m);
|
||||
var firstDue = invoice.CryptoInfo[0].Due;
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
|
@ -381,7 +384,7 @@ namespace BTCPayServer.Tests
|
|||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
invoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
|
||||
Assert.Equal("confirmed", invoice.Status);
|
||||
Assert.Equal("complete", invoice.Status);
|
||||
});
|
||||
|
||||
// BTC crash by 50%
|
||||
|
@ -829,13 +832,13 @@ normal:
|
|||
Assert.Single(btcOnlyInvoice.CryptoInfo);
|
||||
Assert.Equal("BTC",
|
||||
btcOnlyInvoice.CryptoInfo.First().CryptoCode);
|
||||
Assert.Equal(PaymentTypes.BTCLike.ToString(),
|
||||
Assert.Equal("BTC-CHAIN",
|
||||
btcOnlyInvoice.CryptoInfo.First().PaymentType);
|
||||
|
||||
Assert.Equal(2, normalInvoice.CryptoInfo.Length);
|
||||
Assert.Contains(
|
||||
normalInvoice.CryptoInfo,
|
||||
s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
|
||||
s => "BTC-CHAIN" == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
|
||||
s.CryptoCode));
|
||||
|
||||
//test topup option
|
||||
|
|
|
@ -19,6 +19,14 @@
|
|||
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="TestData\OldInvoices.csv" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="TestData\OldInvoices.csv" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
|
||||
|
|
|
@ -162,9 +162,9 @@ namespace BTCPayServer.Tests
|
|||
s.AddLightningNode();
|
||||
s.AddDerivationScheme();
|
||||
|
||||
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
||||
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC-LN");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Equal("Bitcoin (Lightning)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
Assert.Equal("Lightning", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
|
||||
s.Driver.Quit();
|
||||
}
|
||||
|
||||
|
@ -210,8 +210,8 @@ namespace BTCPayServer.Tests
|
|||
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
|
||||
});
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
|
||||
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
|
||||
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
|
||||
.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||
.Destination, Network.RegTest),
|
||||
new Money(0.001m, MoneyUnit.BTC));
|
||||
|
||||
IWebElement closebutton = null;
|
||||
|
|
|
@ -62,13 +62,13 @@ namespace BTCPayServer.Tests
|
|||
var qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
var clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
var payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
var address = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
|
||||
Assert.StartsWith("bcrt", s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text);
|
||||
var address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
|
||||
Assert.StartsWith("bcrt", s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text);
|
||||
Assert.DoesNotContain("lightning=", payUrl);
|
||||
Assert.Equal($"bitcoin:{address}", payUrl);
|
||||
Assert.Equal($"bitcoin:{address}", clipboard);
|
||||
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Lightning_BTC"));
|
||||
s.Driver.ElementDoesNotExist(By.Id("Lightning_BTC-CHAIN"));
|
||||
|
||||
// Details should show exchange rate
|
||||
s.Driver.ToggleCollapse("PaymentDetails");
|
||||
|
@ -84,13 +84,13 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
Assert.StartsWith("lightning:lnurl", payUrl);
|
||||
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC"));
|
||||
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC-CHAIN"));
|
||||
});
|
||||
|
||||
// Default payment method
|
||||
s.GoToHome();
|
||||
invoiceId = s.CreateInvoice(21000, "SATS", defaultPaymentMethod: "BTC_LightningLike");
|
||||
invoiceId = s.CreateInvoice(21000, "SATS", defaultPaymentMethod: "BTC-LN");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
Assert.Equal(2, s.Driver.FindElements(By.CssSelector(".payment-method")).Count);
|
||||
|
@ -99,11 +99,11 @@ namespace BTCPayServer.Tests
|
|||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
address = s.Driver.FindElement(By.CssSelector("#Lightning_BTC_LightningLike .truncate-center-start")).Text;
|
||||
address = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-LN .truncate-center-start")).Text;
|
||||
Assert.Equal($"lightning:{address}", payUrl);
|
||||
Assert.Equal($"lightning:{address}", clipboard);
|
||||
Assert.Equal($"lightning:{address.ToUpperInvariant()}", qrValue);
|
||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC"));
|
||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC-CHAIN"));
|
||||
|
||||
// Lightning amount in sats
|
||||
Assert.Contains("BTC", s.Driver.FindElement(By.Id("AmountDue")).Text);
|
||||
|
@ -155,7 +155,7 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
|
||||
await Task.Delay(200);
|
||||
address = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
|
||||
address = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
|
||||
var amountFraction = "0.00001";
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
||||
Money.Parse(amountFraction));
|
||||
|
@ -271,8 +271,8 @@ namespace BTCPayServer.Tests
|
|||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
|
||||
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text;
|
||||
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
|
||||
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text;
|
||||
Assert.StartsWith($"bitcoin:{copyAddressOnchain}?amount=", payUrl);
|
||||
Assert.Contains("?amount=", payUrl);
|
||||
Assert.Contains("&lightning=", payUrl);
|
||||
|
@ -311,7 +311,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// BIP21 with LN as default payment method
|
||||
s.GoToHome();
|
||||
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
||||
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC-LN");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
|
||||
|
@ -340,8 +340,8 @@ namespace BTCPayServer.Tests
|
|||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
|
||||
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text;
|
||||
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text;
|
||||
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text;
|
||||
Assert.StartsWith($"bitcoin:{copyAddressOnchain}", payUrl);
|
||||
Assert.Contains("?lightning=lnurl", payUrl);
|
||||
Assert.DoesNotContain("amount=", payUrl);
|
||||
|
@ -414,7 +414,7 @@ namespace BTCPayServer.Tests
|
|||
// - NFC/LNURL-W available with just Lightning
|
||||
// - BIP21 works correctly even though Lightning is default payment method
|
||||
s.GoToHome();
|
||||
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
||||
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC-LN");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
|
||||
|
@ -462,8 +462,8 @@ namespace BTCPayServer.Tests
|
|||
iframe.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
|
||||
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
|
||||
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
|
||||
.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||
.Destination, Network.RegTest),
|
||||
new Money(0.001m, MoneyUnit.BTC));
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
|
@ -47,6 +48,7 @@ using NBXplorer.DerivationStrategy;
|
|||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using OpenQA.Selenium.DevTools.V100.DOMSnapshot;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
@ -236,26 +238,24 @@ namespace BTCPayServer.Tests
|
|||
var id = PaymentMethodId.Parse("BTC");
|
||||
var id1 = PaymentMethodId.Parse("BTC-OnChain");
|
||||
var id2 = PaymentMethodId.Parse("BTC-BTCLike");
|
||||
Assert.Equal("LTC-LN", PaymentMethodId.Parse("LTC-LightningNetwork").ToString());
|
||||
Assert.Equal(id, id1);
|
||||
Assert.Equal(id, id2);
|
||||
Assert.Equal("BTC", id.ToString());
|
||||
Assert.Equal("BTC", id.ToString());
|
||||
Assert.Equal("BTC-CHAIN", id.ToString());
|
||||
Assert.Equal("BTC-CHAIN", id.ToString());
|
||||
id = PaymentMethodId.Parse("LTC");
|
||||
Assert.Equal("LTC", id.ToString());
|
||||
Assert.Equal("LTC", id.ToStringNormalized());
|
||||
Assert.Equal("LTC-CHAIN", id.ToString());
|
||||
id = PaymentMethodId.Parse("LTC-offchain");
|
||||
id1 = PaymentMethodId.Parse("LTC-OffChain");
|
||||
id2 = PaymentMethodId.Parse("LTC-LightningLike");
|
||||
Assert.Equal(id, id1);
|
||||
Assert.Equal(id, id2);
|
||||
Assert.Equal("LTC_LightningLike", id.ToString());
|
||||
Assert.Equal("LTC-LightningNetwork", id.ToStringNormalized());
|
||||
Assert.Equal("LTC-LN", id.ToString());
|
||||
#if ALTCOINS
|
||||
id = PaymentMethodId.Parse("XMR");
|
||||
id1 = PaymentMethodId.Parse("XMR-MoneroLike");
|
||||
Assert.Equal(id, id1);
|
||||
Assert.Equal("XMR_MoneroLike", id.ToString());
|
||||
Assert.Equal("XMR", id.ToStringNormalized());
|
||||
Assert.Equal("XMR-CHAIN", id.ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -439,29 +439,31 @@ namespace BTCPayServer.Tests
|
|||
}}
|
||||
}, out items));
|
||||
}
|
||||
|
||||
PaymentMethodId BTC = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
PaymentMethodId LTC = PaymentTypes.CHAIN.GetPaymentMethodId("LTC");
|
||||
[Fact]
|
||||
public void CanCalculateDust()
|
||||
{
|
||||
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
entity.Rates["BTC"] = 34_000m;
|
||||
entity.Rates["LTC"] = 3400m;
|
||||
entity.SetPaymentPrompt(BTC, new PaymentPrompt()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Rate = 34_000m
|
||||
Divisibility = 8
|
||||
});
|
||||
entity.Price = 4000;
|
||||
entity.UpdateTotals();
|
||||
var accounting = entity.GetPaymentMethods().First().Calculate();
|
||||
var accounting = entity.GetPaymentPrompts().First().Calculate();
|
||||
// Exact price should be 0.117647059..., but the payment method round up to one sat
|
||||
Assert.Equal(0.11764706m, accounting.Due);
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(0.11764706m), new Key()),
|
||||
Accounted = true
|
||||
Value = 0.11764706m,
|
||||
Status = PaymentStatus.Settled,
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
Assert.Equal(0.0m, entity.NetDue);
|
||||
|
@ -474,13 +476,13 @@ namespace BTCPayServer.Tests
|
|||
// Now, imagine there is litecoin. It might seem from its
|
||||
// perspecitve that there has been a slight over payment.
|
||||
// However, Calculate() should just cap it to 0.0m
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
entity.SetPaymentPrompt(LTC, new PaymentPrompt()
|
||||
{
|
||||
Currency = "LTC",
|
||||
Rate = 3400m
|
||||
Divisibility = 8
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
var method = entity.GetPaymentMethods().First(p => p.Currency == "LTC");
|
||||
var method = entity.GetPaymentPrompts().First(p => p.Currency == "LTC");
|
||||
accounting = method.Calculate();
|
||||
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||
|
||||
|
@ -492,19 +494,19 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
|
||||
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||
entity.Networks = networkProvider;
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
entity.Rates["BTC"] = 5000m;
|
||||
entity.SetPaymentPrompt(BTC, new PaymentPrompt()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Rate = 5000,
|
||||
NextNetworkFee = Money.Coins(0.1m)
|
||||
PaymentMethodFee = 0.1m,
|
||||
Divisibility = 8
|
||||
});
|
||||
entity.Price = 5000;
|
||||
entity.UpdateTotals();
|
||||
|
||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
||||
var paymentMethod = entity.GetPaymentPrompts().TryGet(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||
var accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(1.0m, accounting.ToSmallestUnit(Money.Satoshis(1.0m).ToDecimal(MoneyUnit.BTC)));
|
||||
Assert.Equal(1.1m, accounting.Due);
|
||||
|
@ -513,10 +515,10 @@ namespace BTCPayServer.Tests
|
|||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(0.5m), new Key()),
|
||||
Value = 0.5m,
|
||||
Rate = 5000,
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
Status = PaymentStatus.Settled,
|
||||
PaymentMethodFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
accounting = paymentMethod.Calculate();
|
||||
|
@ -527,9 +529,9 @@ namespace BTCPayServer.Tests
|
|||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
Value = 0.2m,
|
||||
Status = PaymentStatus.Settled,
|
||||
PaymentMethodFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
accounting = paymentMethod.Calculate();
|
||||
|
@ -539,9 +541,9 @@ namespace BTCPayServer.Tests
|
|||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(0.6m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
Value = 0.6m,
|
||||
Status = PaymentStatus.Settled,
|
||||
PaymentMethodFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
accounting = paymentMethod.Calculate();
|
||||
|
@ -549,75 +551,79 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(1.3m, accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(
|
||||
new PaymentEntity() { Currency = "BTC", Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
new PaymentEntity() { Currency = "BTC", Value = 0.2m, Status = PaymentStatus.Settled });
|
||||
entity.UpdateTotals();
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(1.3m, accounting.TotalDue);
|
||||
|
||||
entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
entity.Price = 5000;
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod() { Currency = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod() { Currency = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m) });
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
entity.Currency = "USD";
|
||||
entity.Rates["BTC"] = 1000m;
|
||||
entity.Rates["LTC"] = 500m;
|
||||
PaymentPromptDictionary paymentMethods =
|
||||
[
|
||||
new PaymentPrompt() { PaymentMethodId = BTC, Currency = "BTC", PaymentMethodFee = 0.1m, Divisibility = 8 },
|
||||
new PaymentPrompt() { PaymentMethodId = LTC, Currency = "LTC", PaymentMethodFee = 0.01m, Divisibility = 8 },
|
||||
];
|
||||
entity.SetPaymentPrompts(paymentMethods);
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
entity.UpdateTotals();
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
paymentMethod = entity.GetPaymentPrompt(BTC);
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(5.1m, accounting.Due);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
paymentMethod = entity.GetPaymentPrompt(LTC);
|
||||
accounting = paymentMethod.Calculate();
|
||||
|
||||
Assert.Equal(10.01m, accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
PaymentMethodId = BTC,
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
Value = 1.0m,
|
||||
Status = PaymentStatus.Settled,
|
||||
PaymentMethodFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(4.2m, accounting.Due);
|
||||
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(1.0m, accounting.PaymentMethodPaid);
|
||||
Assert.Equal(1.0m, accounting.Paid);
|
||||
Assert.Equal(5.2m, accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("LTC"));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(10.01m + 0.1m * 2 - 2.0m /* 8.21m */, accounting.Due);
|
||||
Assert.Equal(0.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(0.0m, accounting.PaymentMethodPaid);
|
||||
Assert.Equal(2.0m, accounting.Paid);
|
||||
Assert.Equal(10.01m + 0.1m * 2, accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
PaymentMethodId = LTC,
|
||||
Currency = "LTC",
|
||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.01m
|
||||
Value = 1.0m,
|
||||
Status = PaymentStatus.Settled,
|
||||
PaymentMethodFee = 0.01m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(4.2m - 0.5m + 0.01m / 2, accounting.Due);
|
||||
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(1.0m, accounting.PaymentMethodPaid);
|
||||
Assert.Equal(1.5m, accounting.Paid);
|
||||
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue); // The fee for LTC added
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("LTC"));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(8.21m - 1.0m + 0.01m, accounting.Due);
|
||||
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(1.0m, accounting.PaymentMethodPaid);
|
||||
Assert.Equal(3.0m, accounting.Paid);
|
||||
Assert.Equal(10.01m + 0.1m * 2 + 0.01m, accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
@ -625,25 +631,26 @@ namespace BTCPayServer.Tests
|
|||
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2.0m).ToDecimal(MoneyUnit.BTC);
|
||||
entity.Payments.Add(new PaymentEntity()
|
||||
{
|
||||
PaymentMethodId = BTC,
|
||||
Currency = "BTC",
|
||||
Output = new TxOut(Money.Coins(remaining), new Key()),
|
||||
Accounted = true,
|
||||
NetworkFee = 0.1m
|
||||
Value = remaining,
|
||||
Status = PaymentStatus.Settled,
|
||||
PaymentMethodFee = 0.1m
|
||||
});
|
||||
entity.UpdateTotals();
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(1.0m + remaining, accounting.CryptoPaid);
|
||||
Assert.Equal(1.0m + remaining, accounting.PaymentMethodPaid);
|
||||
Assert.Equal(1.5m + remaining, accounting.Paid);
|
||||
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue);
|
||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||
Assert.Equal(2, accounting.TxRequired);
|
||||
|
||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("LTC"));
|
||||
accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||
Assert.Equal(1.0m, accounting.PaymentMethodPaid);
|
||||
Assert.Equal(3.0m + remaining * 2, accounting.Paid);
|
||||
// Paying 2 BTC fee, LTC fee removed because fully paid
|
||||
Assert.Equal(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */,
|
||||
|
@ -679,21 +686,21 @@ namespace BTCPayServer.Tests
|
|||
public void CanAcceptInvoiceWithTolerance()
|
||||
{
|
||||
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
entity.Rates["BTC"] = 5000m;
|
||||
entity.SetPaymentPrompt(BTC, new PaymentPrompt()
|
||||
{
|
||||
Currency = "BTC",
|
||||
Rate = 5000,
|
||||
NextNetworkFee = Money.Coins(0.1m)
|
||||
PaymentMethodFee = 0.1m,
|
||||
Divisibility = 8
|
||||
});
|
||||
entity.Price = 5000;
|
||||
entity.PaymentTolerance = 0;
|
||||
entity.UpdateTotals();
|
||||
|
||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
||||
var paymentMethod = entity.GetPaymentPrompts().TryGet(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||
var accounting = paymentMethod.Calculate();
|
||||
Assert.Equal(1.1m, accounting.Due);
|
||||
Assert.Equal(1.1m, accounting.TotalDue);
|
||||
|
@ -2144,63 +2151,48 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
|||
var networkBTC = networkProvider.GetNetwork("BTC");
|
||||
var networkLTC = networkProvider.GetNetwork("LTC");
|
||||
InvoiceEntity invoiceEntity = new InvoiceEntity();
|
||||
invoiceEntity.Networks = networkProvider;
|
||||
invoiceEntity.Currency = "USD";
|
||||
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
invoiceEntity.Price = 100;
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
paymentMethods.Add(new PaymentMethod() { Network = networkBTC, Currency = "BTC", Rate = 10513.44m, }
|
||||
.SetPaymentMethodDetails(
|
||||
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
||||
{
|
||||
NextNetworkFee = Money.Coins(0.00000100m),
|
||||
DepositAddress = dummy
|
||||
}));
|
||||
paymentMethods.Add(new PaymentMethod() { Network = networkLTC, Currency = "LTC", Rate = 216.79m }
|
||||
.SetPaymentMethodDetails(
|
||||
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
||||
{
|
||||
NextNetworkFee = Money.Coins(0.00010000m),
|
||||
DepositAddress = dummy
|
||||
}));
|
||||
invoiceEntity.SetPaymentMethods(paymentMethods);
|
||||
invoiceEntity.Rates.Add("BTC", 10513.44m);
|
||||
invoiceEntity.Rates.Add("LTC", 216.79m);
|
||||
PaymentPromptDictionary paymentMethods =
|
||||
[
|
||||
new () { PaymentMethodId = BTC, Divisibility = 8, Currency = "BTC", PaymentMethodFee = 0.00000100m, ParentEntity = invoiceEntity },
|
||||
new () { PaymentMethodId = LTC, Divisibility = 8, Currency = "LTC", PaymentMethodFee = 0.00010000m, ParentEntity = invoiceEntity },
|
||||
];
|
||||
invoiceEntity.SetPaymentPrompts(paymentMethods);
|
||||
|
||||
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||
var btcId = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var btc = invoiceEntity.GetPaymentPrompt(btcId);
|
||||
var accounting = btc.Calculate();
|
||||
|
||||
invoiceEntity.Payments.Add(
|
||||
new PaymentEntity()
|
||||
{
|
||||
Accounted = true,
|
||||
Status = PaymentStatus.Settled,
|
||||
Currency = "BTC",
|
||||
NetworkFee = 0.00000100m,
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
}
|
||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||
{
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
Output = new TxOut() { Value = Money.Coins(0.00151263m) }
|
||||
}));
|
||||
PaymentMethodFee = 0.00000100m,
|
||||
Value = 0.00151263m,
|
||||
PaymentMethodId = btcId
|
||||
});
|
||||
invoiceEntity.UpdateTotals();
|
||||
accounting = btc.Calculate();
|
||||
invoiceEntity.Payments.Add(
|
||||
new PaymentEntity()
|
||||
{
|
||||
Accounted = true,
|
||||
Status = PaymentStatus.Settled,
|
||||
Currency = "BTC",
|
||||
NetworkFee = 0.00000100m,
|
||||
Network = networkProvider.GetNetwork("BTC")
|
||||
}
|
||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||
{
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
Output = new TxOut() { Value = Money.Coins(accounting.Due) }
|
||||
}));
|
||||
Value = accounting.Due,
|
||||
PaymentMethodFee = 0.00000100m,
|
||||
PaymentMethodId = btcId
|
||||
});
|
||||
invoiceEntity.UpdateTotals();
|
||||
accounting = btc.Calculate();
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||
|
||||
var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||
var ltc = invoiceEntity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("LTC"));
|
||||
accounting = ltc.Calculate();
|
||||
|
||||
Assert.Equal(0.0m, accounting.Due);
|
||||
|
@ -2248,42 +2240,172 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
|||
Assert.Null(metadata.PosData);
|
||||
}
|
||||
|
||||
class CanOldMigrateInvoicesBlobVector
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public JObject Input { get; set; }
|
||||
public JObject Expected { get; set; }
|
||||
public bool SkipRountripTest { get; set; }
|
||||
public Dictionary<string, string> ExpectedProperties { get; set; }
|
||||
}
|
||||
[Fact]
|
||||
public void CanOldMigrateInvoicesBlob()
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
|
||||
int i = 0;
|
||||
var vectors = JsonConvert.DeserializeObject<CanOldMigrateInvoicesBlobVector[]>(File.ReadAllText(TestUtils.GetTestDataFullPath("InvoiceMigrationTestVectors.json")));
|
||||
foreach (var v in vectors)
|
||||
{
|
||||
TestLogs.LogInformation("Test " + i++);
|
||||
object obj = null;
|
||||
if (v.Type == "invoice")
|
||||
{
|
||||
Data.InvoiceData data = new Data.InvoiceData();
|
||||
obj = data;
|
||||
data.Blob2 = v.Input.ToString();
|
||||
data.Migrate();
|
||||
var actual = JObject.Parse(data.Blob2);
|
||||
AssertSameJson(v.Expected, actual);
|
||||
if (!v.SkipRountripTest)
|
||||
{
|
||||
// Check that we get the same as when setting blob again
|
||||
var entity = data.GetBlob();
|
||||
entity.AdditionalData?.Clear();
|
||||
entity.SetPaymentPrompts(entity.GetPaymentPrompts()); // Cleanup
|
||||
data.SetBlob(entity);
|
||||
actual = JObject.Parse(data.Blob2);
|
||||
AssertSameJson(v.Expected, actual);
|
||||
}
|
||||
}
|
||||
else if (v.Type == "payment")
|
||||
{
|
||||
Data.PaymentData data = new Data.PaymentData();
|
||||
//data.
|
||||
obj = data;
|
||||
data.Blob2 = v.Input.ToString();
|
||||
data.Migrate();
|
||||
var actual = JObject.Parse(data.Blob2);
|
||||
AssertSameJson(v.Expected, actual);
|
||||
if (!v.SkipRountripTest)
|
||||
{
|
||||
// Check that we get the same as when setting blob again
|
||||
var entity = data.GetBlob();
|
||||
data.SetBlob(entity);
|
||||
actual = JObject.Parse(data.Blob2);
|
||||
AssertSameJson(v.Expected, actual);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail("Unknown vector type");
|
||||
}
|
||||
if (v.ExpectedProperties is not null)
|
||||
{
|
||||
foreach (var kv in v.ExpectedProperties)
|
||||
{
|
||||
if (kv.Key == "CreatedInMs")
|
||||
{
|
||||
var actual = PaymentData.DateTimeToMilliUnixTime(((DateTimeOffset)obj.GetType().GetProperty("Created").GetValue(obj)).UtcDateTime);
|
||||
Assert.Equal(long.Parse(kv.Value), actual);
|
||||
}
|
||||
else
|
||||
{
|
||||
var actual = obj.GetType().GetProperty(kv.Key).GetValue(obj);
|
||||
Assert.Equal(kv.Value, actual?.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertSameJson(JToken expected, JToken actual, List<string> path = null)
|
||||
{
|
||||
var ok = JToken.DeepEquals(expected, actual);
|
||||
if (ok)
|
||||
return;
|
||||
var e = NormalizeJsonString((JObject)expected);
|
||||
var a = NormalizeJsonString((JObject)actual);
|
||||
Assert.Equal(e, a);
|
||||
}
|
||||
public static string NormalizeJsonString(JObject parsedObject)
|
||||
{
|
||||
var normalizedObject = SortPropertiesAlphabetically(parsedObject);
|
||||
return JsonConvert.SerializeObject(normalizedObject);
|
||||
}
|
||||
|
||||
private static JObject SortPropertiesAlphabetically(JObject original)
|
||||
{
|
||||
var result = new JObject();
|
||||
|
||||
foreach (var property in original.Properties().ToList().OrderBy(p => p.Name))
|
||||
{
|
||||
var value = property.Value as JObject;
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
value = SortPropertiesAlphabetically(value);
|
||||
result.Add(property.Name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(property.Name, property.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseInvoiceEntityDerivationStrategies()
|
||||
{
|
||||
var serializer = BlobSerializer.CreateSerializer(new NBXplorer.NBXplorerNetworkProvider(ChainName.Regtest).GetBTC()).Serializer;
|
||||
// We have 3 ways of serializing the derivation strategies:
|
||||
// through "derivationStrategy", through "derivationStrategies" as a string, through "derivationStrategies" as JObject
|
||||
// Let's check that InvoiceEntity is similar in all cases.
|
||||
var legacy = new JObject()
|
||||
{
|
||||
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf"
|
||||
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf-[p2sh]"
|
||||
};
|
||||
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", CreateNetworkProvider(ChainName.Regtest).BTC);
|
||||
Assert.True(scheme.AccountDerivation is DirectDerivationStrategy { Segwit: true });
|
||||
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf-[p2sh]", CreateNetworkProvider(ChainName.Regtest).BTC);
|
||||
Assert.True(scheme.AccountDerivation is P2SHDerivationStrategy);
|
||||
scheme.Source = "ManualDerivationScheme";
|
||||
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf";
|
||||
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf-[p2sh]";
|
||||
var legacy2 = new JObject()
|
||||
{
|
||||
["derivationStrategies"] = scheme.ToJson()
|
||||
["derivationStrategies"] = new JObject()
|
||||
{
|
||||
["BTC"] = JToken.FromObject(scheme, serializer)
|
||||
}
|
||||
};
|
||||
|
||||
var newformat = new JObject()
|
||||
{
|
||||
["derivationStrategies"] = JObject.Parse(scheme.ToJson())
|
||||
["derivationStrategies"] = new JObject()
|
||||
{
|
||||
["BTC"] = JToken.FromObject(scheme, serializer)
|
||||
}
|
||||
};
|
||||
|
||||
//new BTCPayNetworkProvider(ChainName.Regtest)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var formats = new[] { legacy, legacy2, newformat }
|
||||
.Select(o =>
|
||||
{
|
||||
var entity = JsonConvert.DeserializeObject<InvoiceEntity>(o.ToString());
|
||||
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
|
||||
return entity.DerivationStrategies.ToString();
|
||||
o.Add("currency", "USD");
|
||||
o.Add("price", "0.0");
|
||||
o.Add("cryptoData", new JObject()
|
||||
{
|
||||
["BTC"] = new JObject()
|
||||
});
|
||||
var data = new Data.InvoiceData();
|
||||
data.Blob2 = o.ToString();
|
||||
data.Migrate();
|
||||
var migrated = JObject.Parse(data.Blob2);
|
||||
return migrated["prompts"]["BTC-CHAIN"]["details"]["accountDerivation"].Value<string>();
|
||||
})
|
||||
.ToHashSet();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Assert.Single(formats);
|
||||
var v = Assert.Single(formats);
|
||||
Assert.NotNull(v);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -2292,25 +2414,8 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
|||
var pmi = "\"BTC_hasjdfhasjkfjlajn\"";
|
||||
JsonTextReader reader = new(new StringReader(pmi));
|
||||
reader.Read();
|
||||
Assert.Null(new PaymentMethodIdJsonConverter().ReadJson(reader, typeof(PaymentMethodId), null,
|
||||
JsonSerializer.CreateDefault()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanBeBracefulAfterObsoleteShitcoin()
|
||||
{
|
||||
var blob = new StoreBlob();
|
||||
blob.PaymentMethodCriteria = new List<PaymentMethodCriteria>()
|
||||
{
|
||||
new()
|
||||
{
|
||||
Above = true,
|
||||
Value = new CurrencyValue() {Currency = "BTC", Value = 0.1m},
|
||||
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike)
|
||||
}
|
||||
};
|
||||
var newBlob = new Serializer(null).ToString(blob).Replace("paymentMethod\":\"BTC\"", "paymentMethod\":\"ETH_ZYC\"");
|
||||
Assert.Empty(StoreDataExtensions.GetStoreBlob(new StoreData() { StoreBlob = newBlob }).PaymentMethodCriteria);
|
||||
Assert.Equal("BTC-hasjdfhasjkfjlajn", new PaymentMethodIdJsonConverter().ReadJson(reader, typeof(PaymentMethodId), null,
|
||||
JsonSerializer.CreateDefault()).ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ using Newtonsoft.Json.Linq;
|
|||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
|
@ -228,7 +229,7 @@ namespace BTCPayServer.Tests
|
|||
await Assert.ThrowsAsync<GreenfieldAPIException>(() => newUserClient.GetInvoices(store.Id));
|
||||
|
||||
// if user is a guest or owner, then it should be ok
|
||||
await unrestricted.AddStoreUser(store.Id, new StoreUserData() { UserId = newUser.Id});
|
||||
await unrestricted.AddStoreUser(store.Id, new StoreUserData() { UserId = newUser.Id });
|
||||
await newUserClient.GetInvoices(store.Id);
|
||||
}
|
||||
|
||||
|
@ -947,7 +948,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(payout.Id, payout2.Id);
|
||||
Assert.Equal(destination, payout2.Destination);
|
||||
Assert.Equal(PayoutState.AwaitingApproval, payout.State);
|
||||
Assert.Equal("BTC", payout2.PaymentMethod);
|
||||
Assert.Equal("BTC-CHAIN", payout2.PaymentMethod);
|
||||
Assert.Equal("BTC", payout2.CryptoCode);
|
||||
Assert.Null(payout.PaymentMethodAmount);
|
||||
|
||||
|
@ -1239,7 +1240,7 @@ namespace BTCPayServer.Tests
|
|||
PaymentMethod = "BTC",
|
||||
Amount = 0.0001m,
|
||||
Destination = address.ToString(),
|
||||
|
||||
|
||||
});
|
||||
await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
|
@ -1393,7 +1394,7 @@ namespace BTCPayServer.Tests
|
|||
//check that pmc equals the one we set
|
||||
Assert.Equal(10, pmc.Amount);
|
||||
Assert.True(pmc.Above);
|
||||
Assert.Equal("BTC", pmc.PaymentMethod);
|
||||
Assert.Equal("BTC-CHAIN", pmc.PaymentMethod);
|
||||
Assert.Equal("USD", pmc.CurrencyCode);
|
||||
updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" });
|
||||
Assert.Empty(newStore.PaymentMethodCriteria);
|
||||
|
@ -1428,7 +1429,7 @@ namespace BTCPayServer.Tests
|
|||
// We strip the user's Owner right, so the key should not work
|
||||
using var ctx = tester.PayTester.GetService<Data.ApplicationDbContextFactory>().CreateContext();
|
||||
var storeEntity = await ctx.UserStore.SingleAsync(u => u.ApplicationUserId == user.UserId && u.StoreDataId == newStore.Id);
|
||||
var roleId = (await tester.PayTester.GetService<StoreRepository>().GetStoreRoles(null)).Single(r => r.Role == "Guest").Id;
|
||||
var roleId = (await tester.PayTester.GetService<StoreRepository>().GetStoreRoles(null)).Single(r => r.Role == "Guest").Id;
|
||||
storeEntity.StoreRoleId = roleId;
|
||||
await ctx.SaveChangesAsync();
|
||||
await AssertHttpError(403, async () => await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" }));
|
||||
|
@ -1441,7 +1442,7 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
tester.DeleteStore = false;
|
||||
Assert.Empty(await client.GetStores());
|
||||
|
||||
|
||||
// Archive
|
||||
var archivableStore = await client.CreateStore(new CreateStoreRequest { Name = "Archivable" });
|
||||
Assert.False(archivableStore.Archived);
|
||||
|
@ -1676,8 +1677,8 @@ namespace BTCPayServer.Tests
|
|||
Assert.NotNull(serverInfoData.Version);
|
||||
Assert.NotNull(serverInfoData.Onion);
|
||||
Assert.True(serverInfoData.FullySynched);
|
||||
Assert.Contains("BTC", serverInfoData.SupportedPaymentMethods);
|
||||
Assert.Contains("BTC_LightningLike", serverInfoData.SupportedPaymentMethods);
|
||||
Assert.Contains("BTC-CHAIN", serverInfoData.SupportedPaymentMethods);
|
||||
Assert.Contains("BTC-LN", serverInfoData.SupportedPaymentMethods);
|
||||
Assert.NotNull(serverInfoData.SyncStatus);
|
||||
Assert.Single(serverInfoData.SyncStatus.Select(s => s.CryptoCode == "BTC"));
|
||||
}
|
||||
|
@ -1779,7 +1780,7 @@ namespace BTCPayServer.Tests
|
|||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(Invoice.STATUS_CONFIRMED, (await user.BitPay.GetInvoiceAsync(invoiceId)).Status);
|
||||
Assert.Equal(Invoice.STATUS_COMPLETE, (await user.BitPay.GetInvoiceAsync(invoiceId)).Status);
|
||||
if (!partialPayment)
|
||||
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
|
||||
});
|
||||
|
@ -1981,7 +1982,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
await client.RefundInvoice(user.StoreId, "lol fake invoice id", new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen
|
||||
});
|
||||
});
|
||||
|
@ -1989,7 +1990,7 @@ namespace BTCPayServer.Tests
|
|||
// test validation error for when invoice is not yet in the state in which it can be refunded
|
||||
var apiError = await AssertAPIError("non-refundable", () => client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen
|
||||
}));
|
||||
Assert.Equal("Cannot refund this invoice", apiError.Message);
|
||||
|
@ -2020,7 +2021,7 @@ namespace BTCPayServer.Tests
|
|||
// test RefundVariant.RateThen
|
||||
var pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
|
@ -2031,7 +2032,7 @@ namespace BTCPayServer.Tests
|
|||
// test RefundVariant.CurrentRate
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.CurrentRate
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
|
@ -2041,7 +2042,7 @@ namespace BTCPayServer.Tests
|
|||
// test RefundVariant.Fiat
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.Fiat,
|
||||
Name = "my test name"
|
||||
});
|
||||
|
@ -2055,7 +2056,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.Custom,
|
||||
});
|
||||
});
|
||||
|
@ -2064,7 +2065,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.Custom,
|
||||
CustomAmount = 69420,
|
||||
CustomCurrency = "JPY"
|
||||
|
@ -2076,19 +2077,19 @@ namespace BTCPayServer.Tests
|
|||
// should auto-approve if currencies match
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.Custom,
|
||||
CustomAmount = 0.00069420m,
|
||||
CustomCurrency = "BTC"
|
||||
});
|
||||
Assert.True(pp.AutoApproveClaims);
|
||||
|
||||
|
||||
// test subtract percentage
|
||||
validationError = await AssertValidationError(new[] { "SubtractPercentage" }, async () =>
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen,
|
||||
SubtractPercentage = 101
|
||||
});
|
||||
|
@ -2098,25 +2099,25 @@ namespace BTCPayServer.Tests
|
|||
// should auto-approve
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.RateThen,
|
||||
SubtractPercentage = 6.15m
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
Assert.True(pp.AutoApproveClaims);
|
||||
Assert.Equal(0.9385m, pp.Amount);
|
||||
|
||||
|
||||
// test RefundVariant.OverpaidAmount
|
||||
validationError = await AssertValidationError(new[] { "RefundVariant" }, async () =>
|
||||
{
|
||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.OverpaidAmount
|
||||
});
|
||||
});
|
||||
Assert.Contains("Invoice is not overpaid", validationError.Message);
|
||||
|
||||
|
||||
// should auto-approve
|
||||
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest { Amount = 5000.0m, Currency = "USD" });
|
||||
methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id);
|
||||
|
@ -2134,24 +2135,24 @@ namespace BTCPayServer.Tests
|
|||
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
|
||||
Assert.True(invoice.Status == InvoiceStatus.Settled);
|
||||
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
|
||||
Assert.True(invoice.Status == InvoiceStatus.Settled);
|
||||
Assert.True(invoice.AdditionalStatus == InvoiceExceptionStatus.PaidOver);
|
||||
});
|
||||
|
||||
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.OverpaidAmount
|
||||
});
|
||||
Assert.Equal("BTC", pp.Currency);
|
||||
Assert.True(pp.AutoApproveClaims);
|
||||
Assert.Equal(method.Due, pp.Amount);
|
||||
|
||||
|
||||
// once more with subtract percentage
|
||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||
{
|
||||
PaymentMethod = method.PaymentMethod,
|
||||
PaymentMethod = method.PaymentMethodId,
|
||||
RefundVariant = RefundVariant.OverpaidAmount,
|
||||
SubtractPercentage = 21m
|
||||
});
|
||||
|
@ -2287,8 +2288,8 @@ namespace BTCPayServer.Tests
|
|||
var paymentMethods = await viewOnly.GetInvoicePaymentMethods(user.StoreId, newInvoice.Id);
|
||||
Assert.Single(paymentMethods);
|
||||
var paymentMethod = paymentMethods.First();
|
||||
Assert.Equal("BTC", paymentMethod.PaymentMethod);
|
||||
Assert.Equal("BTC", paymentMethod.CryptoCode);
|
||||
Assert.Equal("BTC-CHAIN", paymentMethod.PaymentMethodId);
|
||||
Assert.Equal("BTC", paymentMethod.Currency);
|
||||
Assert.Empty(paymentMethod.Payments);
|
||||
|
||||
|
||||
|
@ -2455,7 +2456,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Single(paymentMethods);
|
||||
Assert.False(paymentMethods.First().Activated);
|
||||
await client.ActivateInvoicePaymentMethod(user.StoreId, invoice.Id,
|
||||
paymentMethods.First().PaymentMethod);
|
||||
paymentMethods.First().PaymentMethodId);
|
||||
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
|
||||
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "address");
|
||||
|
||||
|
@ -2474,7 +2475,7 @@ namespace BTCPayServer.Tests
|
|||
DefaultPaymentMethod = "BTC_LightningLike"
|
||||
}
|
||||
});
|
||||
Assert.Equal("BTC_LightningLike", invoiceWithDefaultPaymentMethodLN.Checkout.DefaultPaymentMethod);
|
||||
Assert.Equal("BTC-LN", invoiceWithDefaultPaymentMethodLN.Checkout.DefaultPaymentMethod);
|
||||
|
||||
var invoiceWithDefaultPaymentMethodOnChain = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest()
|
||||
|
@ -2487,19 +2488,19 @@ namespace BTCPayServer.Tests
|
|||
DefaultPaymentMethod = "BTC"
|
||||
}
|
||||
});
|
||||
Assert.Equal("BTC", invoiceWithDefaultPaymentMethodOnChain.Checkout.DefaultPaymentMethod);
|
||||
|
||||
Assert.Equal("BTC-CHAIN", invoiceWithDefaultPaymentMethodOnChain.Checkout.DefaultPaymentMethod);
|
||||
|
||||
// reset lazy payment methods
|
||||
store = await client.GetStore(user.StoreId);
|
||||
store.LazyPaymentMethods = false;
|
||||
store = await client.UpdateStore(store.Id,
|
||||
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
||||
Assert.False(store.LazyPaymentMethods);
|
||||
|
||||
|
||||
// use store default payment method
|
||||
store = await client.GetStore(user.StoreId);
|
||||
Assert.Null(store.DefaultPaymentMethod);
|
||||
var storeDefaultPaymentMethod = "BTC-LightningNetwork";
|
||||
var storeDefaultPaymentMethod = "BTC-LN";
|
||||
store.DefaultPaymentMethod = storeDefaultPaymentMethod;
|
||||
store = await client.UpdateStore(store.Id,
|
||||
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
||||
|
@ -2515,7 +2516,7 @@ namespace BTCPayServer.Tests
|
|||
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
||||
}
|
||||
});
|
||||
Assert.Equal(storeDefaultPaymentMethod, invoiceWithStoreDefaultPaymentMethod.Checkout.DefaultPaymentMethod);
|
||||
Assert.Null(invoiceWithStoreDefaultPaymentMethod.Checkout.DefaultPaymentMethod);
|
||||
|
||||
//let's see the overdue amount
|
||||
invoice = await client.CreateInvoice(user.StoreId,
|
||||
|
@ -2724,8 +2725,8 @@ namespace BTCPayServer.Tests
|
|||
Amount = 100,
|
||||
Checkout = new CreateInvoiceRequest.CheckoutOptions
|
||||
{
|
||||
PaymentMethods = new[] { "BTC-LightningNetwork" },
|
||||
DefaultPaymentMethod = "BTC_LightningLike"
|
||||
PaymentMethods = new[] { "BTC-LN" },
|
||||
DefaultPaymentMethod = "BTC-LN"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2751,11 +2752,13 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(PayResult.Ok, resp.Result);
|
||||
Assert.NotNull(resp.Details.PaymentHash);
|
||||
Assert.NotNull(resp.Details.Preimage);
|
||||
|
||||
pm[i] = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, (await invoices[i]).Id));
|
||||
Assert.True(pm[i].AdditionalData.HasValues);
|
||||
Assert.Equal(resp.Details.PaymentHash.ToString(), pm[i].AdditionalData.GetValue("paymentHash"));
|
||||
Assert.Equal(resp.Details.Preimage.ToString(), pm[i].AdditionalData.GetValue("preimage"));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
pm[i] = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, (await invoices[i]).Id));
|
||||
Assert.True(pm[i].AdditionalData.HasValues);
|
||||
Assert.Equal(resp.Details.PaymentHash.ToString(), ((JObject)pm[i].AdditionalData).GetValue("paymentHash"));
|
||||
Assert.Equal(resp.Details.Preimage.ToString(), ((JObject)pm[i].AdditionalData).GetValue("preimage"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2858,10 +2861,10 @@ namespace BTCPayServer.Tests
|
|||
|
||||
var store = await client.CreateStore(new CreateStoreRequest() { Name = "test store" });
|
||||
|
||||
Assert.Empty(await client.GetStoreOnChainPaymentMethods(store.Id));
|
||||
Assert.Empty(await client.GetStorePaymentMethods(store.Id));
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.UpdateStoreOnChainPaymentMethod(store.Id, "BTC", new UpdateOnChainPaymentMethodRequest() { });
|
||||
await viewOnlyClient.UpdateStorePaymentMethod(store.Id, "BTC-CHAIN", new UpdatePaymentMethodRequest() { });
|
||||
});
|
||||
|
||||
var xpriv = new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
|
||||
|
@ -2873,36 +2876,19 @@ namespace BTCPayServer.Tests
|
|||
await client.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC");
|
||||
});
|
||||
|
||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC",
|
||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub })).Addresses.First().Address);
|
||||
|
||||
await AssertValidationError(new[] { "accountKeyPath" }, () => viewOnlyClient.SendHttpRequest<GreenfieldValidationError[]>(path: $"api/v1/stores/{store.Id}/payment-methods/onchain/BTC/preview", method: HttpMethod.Post,
|
||||
bodyPayload: JObject.Parse("{\"accountKeyPath\": \"0/1\"}")));
|
||||
|
||||
var method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
|
||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub });
|
||||
|
||||
Assert.Equal(xpub, method.DerivationScheme);
|
||||
|
||||
method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
|
||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub, Label = "lol", AccountKeyPath = RootedKeyPath.Parse("01020304/1/2/3") });
|
||||
|
||||
method = await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
|
||||
Assert.Equal("lol", method.Label);
|
||||
Assert.Equal(RootedKeyPath.Parse("01020304/1/2/3"), method.AccountKeyPath);
|
||||
Assert.Equal(xpub, method.DerivationScheme);
|
||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC", xpub)).Addresses.First().Address);
|
||||
|
||||
var method = await client.UpdateStorePaymentMethod(store.Id, "BTC-CHAIN", new UpdatePaymentMethodRequest() { Enabled = true, Config = JValue.CreateString(xpub.ToString())});
|
||||
|
||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC")).Addresses.First().Address);
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await viewOnlyClient.RemoveStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||
});
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await client.RemoveStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await client.GetStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||
});
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
|
@ -2923,12 +2909,12 @@ namespace BTCPayServer.Tests
|
|||
var allMnemonic = new Mnemonic("all all all all all all all all all all all all");
|
||||
|
||||
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await client.RemoveStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||
var generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, });
|
||||
|
||||
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||
Assert.Equal(generateResponse.DerivationScheme, xpub);
|
||||
Assert.Equal(generateResponse.Config.AccountDerivation, xpub);
|
||||
|
||||
await AssertAPIError("already-configured", async () =>
|
||||
{
|
||||
|
@ -2936,22 +2922,22 @@ namespace BTCPayServer.Tests
|
|||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, });
|
||||
});
|
||||
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await client.RemoveStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() { });
|
||||
Assert.NotEqual(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||
Assert.Equal(generateResponse.Mnemonic.DeriveExtKey().Derive(KeyPath.Parse("m/84'/1'/0'")).Neuter().ToString(Network.RegTest), generateResponse.DerivationScheme);
|
||||
Assert.Equal(generateResponse.Mnemonic.DeriveExtKey().Derive(KeyPath.Parse("m/84'/1'/0'")).Neuter().ToString(Network.RegTest), generateResponse.Config.AccountDerivation);
|
||||
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await client.RemoveStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, AccountNumber = 1 });
|
||||
|
||||
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||
|
||||
Assert.Equal(new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
|
||||
.Derive(KeyPath.Parse("m/84'/1'/1'")).Neuter().ToString(Network.RegTest), generateResponse.DerivationScheme);
|
||||
.Derive(KeyPath.Parse("m/84'/1'/1'")).Neuter().ToString(Network.RegTest), generateResponse.Config.AccountDerivation);
|
||||
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await client.RemoveStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() { WordList = Wordlist.Japanese, WordCount = WordCount.TwentyFour });
|
||||
|
||||
|
@ -2978,26 +2964,29 @@ namespace BTCPayServer.Tests
|
|||
var viewOnlyClient = await admin.CreateClient(Policies.CanViewStoreSettings);
|
||||
var store = await adminClient.GetStore(admin.StoreId);
|
||||
|
||||
Assert.Empty(await adminClient.GetStoreLightningNetworkPaymentMethods(store.Id));
|
||||
Assert.Empty(await adminClient.GetStorePaymentMethods(store.Id));
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest() { });
|
||||
await viewOnlyClient.UpdateStorePaymentMethod(store.Id, "BTC-LN", new UpdatePaymentMethodRequest() { });
|
||||
});
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
|
||||
await adminClient.GetStorePaymentMethod(store.Id, "BTC-LN");
|
||||
});
|
||||
await admin.RegisterLightningNodeAsync("BTC", false);
|
||||
|
||||
var method = await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
|
||||
var method = await adminClient.GetStorePaymentMethod(store.Id, "BTC-LN");
|
||||
Assert.Null(method.Config);
|
||||
method = await adminClient.GetStorePaymentMethod(store.Id, "BTC-LN", includeConfig: true);
|
||||
Assert.NotNull(method.Config);
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await viewOnlyClient.RemoveStorePaymentMethod(store.Id, "BTC-LN");
|
||||
});
|
||||
await adminClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await adminClient.RemoveStorePaymentMethod(store.Id, "BTC-LN");
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await adminClient.GetStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await adminClient.GetStorePaymentMethod(store.Id, "BTC-LN");
|
||||
});
|
||||
|
||||
|
||||
|
@ -3016,33 +3005,45 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
var ex = await AssertValidationError(new[] { "ConnectionString" }, async () =>
|
||||
{
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await adminClient.UpdateStorePaymentMethod(store.Id, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
ConnectionString = forbidden,
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = forbidden
|
||||
},
|
||||
Enabled = true
|
||||
});
|
||||
});
|
||||
Assert.Contains("btcpay.server.canmodifyserversettings", ex.Message);
|
||||
// However, the other client should work because he has `btcpay.server.canmodifyserversettings`
|
||||
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await admin2Client.UpdateStorePaymentMethod(admin2.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
ConnectionString = forbidden,
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = forbidden
|
||||
},
|
||||
Enabled = true
|
||||
});
|
||||
}
|
||||
// Allowed ip should be ok
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await adminClient.UpdateStorePaymentMethod(store.Id, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
ConnectionString = "type=clightning;server=tcp://8.8.8.8",
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = "type=clightning;server=tcp://8.8.8.8"
|
||||
},
|
||||
Enabled = true
|
||||
});
|
||||
// If we strip the admin's right, he should not be able to set unsafe anymore, even if the API key is still valid
|
||||
await admin2.MakeAdmin(false);
|
||||
await AssertValidationError(new[] { "ConnectionString" }, async () =>
|
||||
{
|
||||
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await admin2Client.UpdateStorePaymentMethod(admin2.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
ConnectionString = "type=clightning;server=tcp://127.0.0.1",
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = "type=clightning;server=tcp://127.0.0.1"
|
||||
},
|
||||
Enabled = true
|
||||
});
|
||||
});
|
||||
|
@ -3056,52 +3057,72 @@ namespace BTCPayServer.Tests
|
|||
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await nonAdminUserClient.GetStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC");
|
||||
await nonAdminUserClient.GetStorePaymentMethod(nonAdminUser.StoreId, "BTC-LN");
|
||||
});
|
||||
await AssertPermissionError("btcpay.server.canuseinternallightningnode", () => nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await AssertPermissionError("btcpay.server.canuseinternallightningnode", () => nonAdminUserClient.UpdateStorePaymentMethod(nonAdminUser.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Enabled = method.Enabled,
|
||||
ConnectionString = method.ConnectionString
|
||||
Config = new JObject()
|
||||
{
|
||||
["internalNodeRef"] = "Internal Node"
|
||||
}
|
||||
}));
|
||||
|
||||
settings = await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>();
|
||||
settings.AllowLightningInternalNodeForAll = true;
|
||||
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
|
||||
|
||||
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await nonAdminUserClient.UpdateStorePaymentMethod(nonAdminUser.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Enabled = method.Enabled,
|
||||
ConnectionString = method.ConnectionString
|
||||
Config = new JObject()
|
||||
{
|
||||
["internalNodeRef"] = "Internal Node"
|
||||
}
|
||||
});
|
||||
|
||||
// NonAdmin can't set to internal node in AllowLightningInternalNodeForAll is false, but can do other connection string
|
||||
settings = (await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
||||
settings.AllowLightningInternalNodeForAll = false;
|
||||
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
|
||||
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await nonAdminUserClient.UpdateStorePaymentMethod(nonAdminUser.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Enabled = true,
|
||||
ConnectionString = "type=clightning;server=tcp://8.8.8.8"
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = "type=clightning;server=tcp://8.8.8.8"
|
||||
}
|
||||
});
|
||||
await AssertPermissionError("btcpay.server.canuseinternallightningnode", () => nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await AssertPermissionError("btcpay.server.canuseinternallightningnode", () => nonAdminUserClient.UpdateStorePaymentMethod(nonAdminUser.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Enabled = true,
|
||||
ConnectionString = "Internal Node"
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = "Internal Node"
|
||||
}
|
||||
}));
|
||||
// NonAdmin add admin as owner of the store
|
||||
await nonAdminUser.AddOwner(admin.UserId);
|
||||
// Admin turn on Internal node
|
||||
adminClient = await admin.CreateClient(Policies.CanModifyStoreSettings, Policies.CanUseInternalLightningNode);
|
||||
var data = await adminClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
var data = await adminClient.UpdateStorePaymentMethod(nonAdminUser.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Enabled = method.Enabled,
|
||||
ConnectionString = "Internal Node"
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = "Internal Node"
|
||||
}
|
||||
});
|
||||
Assert.NotNull(data);
|
||||
Assert.NotNull(data.Config["internalNodeRef"]?.Value<string>());
|
||||
// Make sure that the nonAdmin can toggle enabled, ConnectionString unchanged.
|
||||
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest()
|
||||
await nonAdminUserClient.UpdateStorePaymentMethod(nonAdminUser.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Enabled = !data.Enabled,
|
||||
ConnectionString = "Internal Node"
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = "Internal Node"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3385,59 +3406,67 @@ namespace BTCPayServer.Tests
|
|||
|
||||
Assert.Empty(await adminClient.GetStorePaymentMethods(store.Id));
|
||||
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(admin.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest("Internal Node", true));
|
||||
|
||||
void VerifyLightning(Dictionary<string, GenericPaymentMethodData> dictionary)
|
||||
await adminClient.UpdateStorePaymentMethod(admin.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
|
||||
var lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
Assert.Equal("Internal Node", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
||||
Enabled = true,
|
||||
Config = new JObject()
|
||||
{
|
||||
{"connectionString", "Internal Node" }
|
||||
}
|
||||
});
|
||||
|
||||
void VerifyLightning(GenericPaymentMethodData[] methods)
|
||||
{
|
||||
var m = Assert.Single(methods.Where(m => m.PaymentMethodId == "BTC-LN"));
|
||||
Assert.Equal("Internal Node", m.Config["internalNodeRef"].Value<string>());
|
||||
}
|
||||
|
||||
var methods = await adminClient.GetStorePaymentMethods(store.Id);
|
||||
var methods = await adminClient.GetStorePaymentMethods(store.Id, includeConfig: true);
|
||||
Assert.Single(methods);
|
||||
VerifyLightning(methods);
|
||||
|
||||
var randK = new Mnemonic(Wordlist.English, WordCount.Twelve).DeriveExtKey().Neuter().ToString(Network.RegTest);
|
||||
await adminClient.UpdateStoreOnChainPaymentMethod(admin.StoreId, "BTC",
|
||||
new UpdateOnChainPaymentMethodRequest(true, randK, "testing", null));
|
||||
var wallet = await adminClient.GenerateOnChainWallet(store.Id, "BTC", new GenerateOnChainWalletRequest() { });
|
||||
|
||||
void VerifyOnChain(Dictionary<string, GenericPaymentMethodData> dictionary)
|
||||
void VerifyOnChain(GenericPaymentMethodData[] dictionary)
|
||||
{
|
||||
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), out var item));
|
||||
var paymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
|
||||
Assert.Equal(randK, paymentMethodBaseData.DerivationScheme);
|
||||
var m = Assert.Single(methods.Where(m => m.PaymentMethodId == "BTC-CHAIN"));
|
||||
var paymentMethodBaseData = Assert.IsType<JObject>(m.Config);
|
||||
Assert.Equal(wallet.Config.AccountDerivation, paymentMethodBaseData["accountDerivation"].Value<string>());
|
||||
}
|
||||
|
||||
methods = await adminClient.GetStorePaymentMethods(store.Id);
|
||||
Assert.Equal(2, methods.Count);
|
||||
methods = await adminClient.GetStorePaymentMethods(store.Id, includeConfig: true);
|
||||
Assert.Equal(2, methods.Length);
|
||||
VerifyLightning(methods);
|
||||
VerifyOnChain(methods);
|
||||
|
||||
var connStr = tester.GetLightningConnectionString(LightningConnectionType.CLightning, true);
|
||||
await adminClient.UpdateStorePaymentMethod(store.Id, "BTC-LN",
|
||||
new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Enabled = true,
|
||||
Config = new JObject()
|
||||
{
|
||||
["connectionString"] = connStr
|
||||
}
|
||||
});
|
||||
await AssertPermissionError("btcpay.store.canmodifystoresettings", () => viewerOnlyClient.GetStorePaymentMethods(store.Id, includeConfig: true));
|
||||
methods = await adminClient.GetStorePaymentMethods(store.Id, includeConfig: true);
|
||||
|
||||
methods = await viewerOnlyClient.GetStorePaymentMethods(store.Id);
|
||||
|
||||
VerifyLightning(methods);
|
||||
|
||||
|
||||
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC",
|
||||
new UpdateLightningNetworkPaymentMethodRequest(
|
||||
tester.GetLightningConnectionString(LightningConnectionType.CLightning, true), true));
|
||||
methods = await viewerOnlyClient.GetStorePaymentMethods(store.Id);
|
||||
|
||||
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
|
||||
var lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
Assert.Equal("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
||||
|
||||
|
||||
Assert.Equal(connStr, methods.FirstOrDefault(m => m.PaymentMethodId == "BTC-LN")?.Config["connectionString"].Value<string>());
|
||||
methods = await adminClient.GetStorePaymentMethods(store.Id);
|
||||
Assert.Null(methods.FirstOrDefault(m => m.PaymentMethodId == "BTC-LN").Config);
|
||||
await this.AssertValidationError(["paymentMethodId"], () => adminClient.RemoveStorePaymentMethod(store.Id, "LOL"));
|
||||
await adminClient.RemoveStorePaymentMethod(store.Id, "BTC-LN");
|
||||
|
||||
Assert.True(methods.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out item));
|
||||
lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
Assert.NotEqual("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
||||
|
||||
|
||||
// Alternative way of setting the connection string
|
||||
await adminClient.UpdateStorePaymentMethod(store.Id, "BTC-LN",
|
||||
new UpdatePaymentMethodRequest()
|
||||
{
|
||||
Enabled = true,
|
||||
Config = JValue.CreateString("Internal Node")
|
||||
});
|
||||
methods = await adminClient.GetStorePaymentMethods(store.Id, includeConfig: true);
|
||||
Assert.Equal("Internal Node", methods.FirstOrDefault(m => m.PaymentMethodId == "BTC-LN").Config["internalNodeRef"].Value<string>());
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
@ -3657,11 +3686,11 @@ namespace BTCPayServer.Tests
|
|||
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
||||
Assert.False((await adminClient.GetUserByIdOrEmail(admin.UserId)).RequiresApproval);
|
||||
Assert.Empty(await adminClient.GetNotifications());
|
||||
|
||||
|
||||
// require approval
|
||||
var settings = tester.PayTester.GetService<SettingsRepository>();
|
||||
await settings.UpdateSetting(new PoliciesSettings { LockSubscription = false, RequiresUserApproval = true });
|
||||
|
||||
|
||||
// new user needs approval
|
||||
var unapprovedUser = tester.NewAccount();
|
||||
await unapprovedUser.GrantAccessAsync();
|
||||
|
@ -3684,7 +3713,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.True((await adminClient.GetUserByIdOrEmail(unapprovedUser.UserId)).Approved);
|
||||
Assert.True((await unapprovedUserApiKeyClient.GetCurrentUser()).Approved);
|
||||
Assert.True((await unapprovedUserBasicAuthClient.GetCurrentUser()).Approved);
|
||||
|
||||
|
||||
// un-approve
|
||||
Assert.True(await adminClient.ApproveUser(unapprovedUser.UserId, false, CancellationToken.None));
|
||||
Assert.False((await adminClient.GetUserByIdOrEmail(unapprovedUser.UserId)).Approved);
|
||||
|
@ -3696,10 +3725,10 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
await unapprovedUserBasicAuthClient.GetCurrentUser();
|
||||
});
|
||||
|
||||
|
||||
// reset policies to not require approval
|
||||
await settings.UpdateSetting(new PoliciesSettings { LockSubscription = false, RequiresUserApproval = false });
|
||||
|
||||
|
||||
// new user does not need approval
|
||||
var newUser = tester.NewAccount();
|
||||
await newUser.GrantAccessAsync();
|
||||
|
@ -3710,14 +3739,14 @@ namespace BTCPayServer.Tests
|
|||
Assert.False((await newUserBasicAuthClient.GetCurrentUser()).RequiresApproval);
|
||||
Assert.False((await newUserBasicAuthClient.GetCurrentUser()).Approved);
|
||||
Assert.Single(await adminClient.GetNotifications(false));
|
||||
|
||||
|
||||
// try unapproving user which does not have the RequiresApproval flag
|
||||
await AssertAPIError("invalid-state", async () =>
|
||||
{
|
||||
await adminClient.ApproveUser(newUser.UserId, false, CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
|
@ -3773,7 +3802,7 @@ namespace BTCPayServer.Tests
|
|||
Amount = 0.0001m,
|
||||
Metadata = JObject.FromObject(new
|
||||
{
|
||||
source ="apitest",
|
||||
source = "apitest",
|
||||
sourceLink = "https://chocolate.com"
|
||||
})
|
||||
});
|
||||
|
@ -3782,16 +3811,16 @@ namespace BTCPayServer.Tests
|
|||
source = "apitest",
|
||||
sourceLink = "https://chocolate.com"
|
||||
}).ToString());
|
||||
|
||||
|
||||
payout =
|
||||
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
|
||||
|
||||
|
||||
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
|
||||
{
|
||||
source = "apitest",
|
||||
sourceLink = "https://chocolate.com"
|
||||
}).ToString());
|
||||
|
||||
|
||||
customerInvoice = await tester.CustomerLightningD.CreateInvoice(LightMoney.FromUnit(10, LightMoneyUnit.Satoshi),
|
||||
Guid.NewGuid().ToString(), TimeSpan.FromDays(40));
|
||||
var payout2 = await adminClient.CreatePayout(admin.StoreId,
|
||||
|
@ -3885,7 +3914,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(3600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||
|
||||
var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId));
|
||||
Assert.Equal("BTC", Assert.Single(tpGen.PaymentMethods));
|
||||
Assert.Equal("BTC-CHAIN", Assert.Single(tpGen.PaymentMethods));
|
||||
//still too poor to process any payouts
|
||||
Assert.Empty(await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||
|
||||
|
@ -3921,10 +3950,10 @@ namespace BTCPayServer.Tests
|
|||
uint256 txid = null;
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m) + fee);
|
||||
}, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
||||
await tester.PayTester.GetService<PayoutProcessorService>().Restart(new PayoutProcessorService.PayoutProcessorQuery(admin.StoreId, "BTC"));
|
||||
await tester.PayTester.GetService<PayoutProcessorService>().Restart(new PayoutProcessorService.PayoutProcessorQuery(admin.StoreId, PaymentMethodId.Parse("BTC")));
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||
|
@ -3935,24 +3964,24 @@ namespace BTCPayServer.Tests
|
|||
// settings that were added later
|
||||
var settings =
|
||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.False( settings.ProcessNewPayoutsInstantly);
|
||||
Assert.False(settings.ProcessNewPayoutsInstantly);
|
||||
Assert.Equal(0m, settings.Threshold);
|
||||
|
||||
|
||||
//let's use the ProcessNewPayoutsInstantly so that it will trigger instantly
|
||||
|
||||
|
||||
settings.IntervalSeconds = TimeSpan.FromDays(1);
|
||||
settings.ProcessNewPayoutsInstantly = true;
|
||||
|
||||
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
txid = await tester.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create((await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(1m) + fee);
|
||||
}, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
||||
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", settings);
|
||||
settings =
|
||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.True( settings.ProcessNewPayoutsInstantly);
|
||||
Assert.True(settings.ProcessNewPayoutsInstantly);
|
||||
|
||||
var pluginHookService = tester.PayTester.GetService<IPluginHookService>();
|
||||
var beforeHookTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
@ -4008,23 +4037,23 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
//let's test the threshold limiter
|
||||
settings.Threshold = 0.5m;
|
||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", settings);
|
||||
|
||||
|
||||
//quick test: when updating processor, it processes instantly
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
|
||||
settings =
|
||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||
Assert.Equal(0.5m, settings.Threshold);
|
||||
|
||||
|
||||
//create a payout that should not be processed straight away due to threshold
|
||||
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
var payoutThatShouldNotBeProcessedStraightAway = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
|
@ -4034,13 +4063,13 @@ namespace BTCPayServer.Tests
|
|||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Single(payouts.Where(data => data.State == PayoutState.AwaitingPayment && data.Id == payoutThatShouldNotBeProcessedStraightAway.Id));
|
||||
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
var payoutThatShouldNotBeProcessedStraightAway2 = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
|
@ -4050,14 +4079,14 @@ namespace BTCPayServer.Tests
|
|||
PaymentMethod = "BTC",
|
||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||
});
|
||||
|
||||
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Equal(2, payouts.Count(data => data.State == PayoutState.AwaitingPayment &&
|
||||
(data.Id == payoutThatShouldNotBeProcessedStraightAway.Id || data.Id == payoutThatShouldNotBeProcessedStraightAway2.Id)));
|
||||
|
||||
|
||||
beforeHookTcs = new TaskCompletionSource();
|
||||
afterHookTcs = new TaskCompletionSource();
|
||||
await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||
|
@ -4070,7 +4099,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
|
||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||
|
||||
|
|
|
@ -381,6 +381,7 @@ namespace BTCPayServer.Tests
|
|||
return Task.CompletedTask;
|
||||
});
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
var handler = s.Server.PayTester.GetService<PaymentMethodHandlerDictionary>().GetBitcoinHandler("BTC");
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoice = await invoiceRepository.GetInvoice(invoiceId);
|
||||
|
@ -389,19 +390,13 @@ namespace BTCPayServer.Tests
|
|||
var originalPayment = payments[0];
|
||||
var coinjoinPayment = payments[1];
|
||||
Assert.Equal(-1,
|
||||
((BitcoinLikePaymentData)originalPayment.GetCryptoPaymentData()).ConfirmationCount);
|
||||
handler.ParsePaymentDetails(originalPayment.Details).ConfirmationCount);
|
||||
Assert.Equal(0,
|
||||
((BitcoinLikePaymentData)coinjoinPayment.GetCryptoPaymentData()).ConfirmationCount);
|
||||
handler.ParsePaymentDetails(coinjoinPayment.Details).ConfirmationCount);
|
||||
Assert.False(originalPayment.Accounted);
|
||||
Assert.True(coinjoinPayment.Accounted);
|
||||
Assert.Equal(((BitcoinLikePaymentData)originalPayment.GetCryptoPaymentData()).Value,
|
||||
((BitcoinLikePaymentData)coinjoinPayment.GetCryptoPaymentData()).Value);
|
||||
Assert.Equal(originalPayment.GetCryptoPaymentData()
|
||||
.AssertType<BitcoinLikePaymentData>()
|
||||
.Value,
|
||||
coinjoinPayment.GetCryptoPaymentData()
|
||||
.AssertType<BitcoinLikePaymentData>()
|
||||
.Value);
|
||||
Assert.Equal(originalPayment.Value,
|
||||
coinjoinPayment.Value);
|
||||
});
|
||||
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
|
@ -929,10 +924,9 @@ retry:
|
|||
tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
|
||||
var senderStore = await tester.PayTester.StoreRepository.FindStore(senderUser.StoreId);
|
||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
||||
var derivationSchemeSettings = senderStore.GetSupportedPaymentMethods(tester.NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>().SingleOrDefault(settings =>
|
||||
settings.PaymentId == paymentMethodId);
|
||||
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
var derivationSchemeSettings = senderStore.GetPaymentMethodConfig<DerivationSchemeSettings>(paymentMethodId, handlers);
|
||||
|
||||
ReceivedCoin[] senderCoins = null;
|
||||
ReceivedCoin coin = null;
|
||||
|
@ -1138,14 +1132,14 @@ retry:
|
|||
//broadcast the payjoin
|
||||
var res = (await tester.ExplorerClient.BroadcastAsync(Invoice7Coin6Response1TxSigned));
|
||||
Assert.True(res.Success);
|
||||
|
||||
var handler = handlers.GetBitcoinHandler("BTC");
|
||||
// Paid with coinjoin
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoiceEntity.Status);
|
||||
Assert.Contains(invoiceEntity.GetPayments(false), p => p.Accounted &&
|
||||
((BitcoinLikePaymentData)p.GetCryptoPaymentData()).PayjoinInformation is null);
|
||||
handler.ParsePaymentDetails(p.Details).PayjoinInformation is null);
|
||||
});
|
||||
////Assert.Contains(receiverWalletPayJoinState.GetRecords(), item => item.InvoiceId == invoice7.Id && item.TxSeen);
|
||||
|
||||
|
@ -1174,7 +1168,7 @@ retry:
|
|||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||
Assert.Equal(InvoiceStatusLegacy.New, invoiceEntity.Status);
|
||||
Assert.True(invoiceEntity.GetPayments(false).All(p => !p.Accounted));
|
||||
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData(false).First().PayjoinInformation.ContributedOutPoints[0];
|
||||
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData(handler, false).First().PayjoinInformation.ContributedOutPoints[0];
|
||||
});
|
||||
var payjoinRepository = tester.PayTester.GetService<UTXOLocker>();
|
||||
// The outpoint should now be available for next pj selection
|
||||
|
|
|
@ -27,6 +27,7 @@ using BTCPayServer.Views.Wallets;
|
|||
using ExchangeSharp;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
@ -1550,17 +1551,16 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.0m));
|
||||
}
|
||||
|
||||
var handlers = s.Server.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
var targetTx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.2m));
|
||||
var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);
|
||||
var spentOutpoint = new OutPoint(targetTx,
|
||||
tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var store = await s.Server.PayTester.StoreRepository.FindStore(storeId);
|
||||
var x = store.GetSupportedPaymentMethods(s.Server.NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Single(settings => settings.PaymentId.CryptoCode == walletId.CryptoCode);
|
||||
var x = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||
var wallet = s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode);
|
||||
wallet.InvalidateCache(x.AccountDerivation);
|
||||
Assert.Contains(
|
||||
|
@ -1821,7 +1821,8 @@ namespace BTCPayServer.Tests
|
|||
|
||||
var invoiceId = s.CreateInvoice(storeId);
|
||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var address = invoice.GetPaymentPrompt(btc).Destination;
|
||||
|
||||
//wallet should have been imported to bitcoin core wallet in watch only mode.
|
||||
var result =
|
||||
|
@ -1833,7 +1834,7 @@ namespace BTCPayServer.Tests
|
|||
//lets import and save private keys
|
||||
invoiceId = s.CreateInvoice(storeId);
|
||||
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
address = invoice.GetPaymentPrompt(btc).Destination;
|
||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(
|
||||
BitcoinAddress.Create(address, Network.RegTest));
|
||||
//spendable from bitcoin core wallet!
|
||||
|
@ -1895,8 +1896,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.EndsWith("psbt/ready", s.Driver.Url);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
Assert.Equal(walletTransactionUri.ToString(), s.Driver.Url);
|
||||
|
||||
var bip21 = invoice.EntityToDTO().CryptoInfo.First().PaymentUrls.BIP21;
|
||||
var bip21 = invoice.EntityToDTO(s.Server.PayTester.GetService<Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension>>()).CryptoInfo.First().PaymentUrls.BIP21;
|
||||
//let's make bip21 more interesting
|
||||
bip21 += "&label=Solid Snake&message=Snake? Snake? SNAAAAKE!";
|
||||
var parsedBip21 = new BitcoinUrlBuilder(bip21, Network.RegTest);
|
||||
|
@ -2257,7 +2257,7 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PaymentTypes.LN.GetPaymentMethodId("BTC")}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
|
||||
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||
|
@ -2267,7 +2267,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
s.FindAlertMessage();
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PaymentTypes.LN.GetPaymentMethodId("BTC")}-view")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||
if (!s.Driver.PageSource.Contains(bolt))
|
||||
|
@ -2277,7 +2277,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PaymentTypes.LN.GetPaymentMethodId("BTC")}-view")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||
Assert.Contains(bolt, s.Driver.PageSource);
|
||||
|
@ -2889,6 +2889,7 @@ namespace BTCPayServer.Tests
|
|||
public async Task CanUseLNURL()
|
||||
{
|
||||
using var s = CreateSeleniumTester();
|
||||
s.Server.DeleteStore = false;
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
await s.Server.EnsureChannelsSetup();
|
||||
|
@ -3028,7 +3029,7 @@ namespace BTCPayServer.Tests
|
|||
// Check that pull payment has lightning option
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike), PaymentMethodId.Parse(Assert.Single(s.Driver.FindElements(By.CssSelector("input[name='PaymentMethods']"))).GetAttribute("value")));
|
||||
Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), PaymentMethodId.Parse(Assert.Single(s.Driver.FindElements(By.CssSelector("input[name='PaymentMethods']"))).GetAttribute("value")));
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
||||
|
@ -3053,7 +3054,7 @@ namespace BTCPayServer.Tests
|
|||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
payouts[0].Click();
|
||||
s.Driver.FindElement(By.Id("BTC_LightningLike-view")).Click();
|
||||
s.Driver.FindElement(By.Id("BTC-LN-view")).Click();
|
||||
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
||||
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||
|
@ -3164,14 +3165,14 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(2, invoices.Length);
|
||||
foreach (var i in invoices)
|
||||
{
|
||||
var lightningPaymentMethod = i.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.LNURLPay));
|
||||
var paymentMethodDetails =
|
||||
lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
var prompt = i.GetPaymentPrompt(PaymentTypes.LNURL.GetPaymentMethodId("BTC"));
|
||||
var handlers = s.Server.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
var details = (LNURLPayPaymentMethodDetails)handlers.ParsePaymentPromptDetails(prompt);
|
||||
Assert.Contains(
|
||||
paymentMethodDetails.ConsumedLightningAddress,
|
||||
details.ConsumedLightningAddress,
|
||||
new[] { lnaddress1, lnaddress2 });
|
||||
|
||||
if (paymentMethodDetails.ConsumedLightningAddress == lnaddress2)
|
||||
if (details.ConsumedLightningAddress == lnaddress2)
|
||||
{
|
||||
Assert.Equal("lol", i.Metadata.AdditionalData["test"].Value<string>());
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
string connectionString = null;
|
||||
if (connectionType is null)
|
||||
return LightningSupportedPaymentMethod.InternalNode;
|
||||
return LightningPaymentMethodConfig.InternalNode;
|
||||
if (connectionType == LightningConnectionType.CLightning)
|
||||
{
|
||||
if (isMerchant)
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
|
@ -18,9 +19,11 @@ using BTCPayServer.Lightning;
|
|||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
|
@ -29,6 +32,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
|
@ -148,15 +152,6 @@ namespace BTCPayServer.Tests
|
|||
await storeController.GeneralSettings(settings);
|
||||
}
|
||||
|
||||
public async Task ModifyWalletSettings(Action<WalletSettingsViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<UIStoresController>();
|
||||
var response = await storeController.WalletSettings(StoreId, "BTC");
|
||||
WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model;
|
||||
modify(walletSettings);
|
||||
storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task ModifyOnchainPaymentSettings(Action<WalletSettingsViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<UIStoresController>();
|
||||
|
@ -164,6 +159,7 @@ namespace BTCPayServer.Tests
|
|||
WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model;
|
||||
modify(walletSettings);
|
||||
storeController.UpdatePaymentSettings(walletSettings).GetAwaiter().GetResult();
|
||||
storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||
|
@ -295,7 +291,7 @@ namespace BTCPayServer.Tests
|
|||
var storeController = GetController<UIStoresController>();
|
||||
|
||||
var connectionString = parent.GetLightningConnectionString(connectionType, isMerchant);
|
||||
var nodeType = connectionString == LightningSupportedPaymentMethod.InternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
var nodeType = connectionString == LightningPaymentMethodConfig.InternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
|
||||
var vm = new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true };
|
||||
await storeController.SetupLightningNode(storeId ?? StoreId,
|
||||
|
@ -373,8 +369,9 @@ namespace BTCPayServer.Tests
|
|||
var pjClient = parent.PayTester.GetService<PayjoinClient>();
|
||||
var storeRepository = parent.PayTester.GetService<StoreRepository>();
|
||||
var store = await storeRepository.FindStore(StoreId);
|
||||
var settings = store.GetSupportedPaymentMethods(parent.NetworkProvider).OfType<DerivationSchemeSettings>()
|
||||
.First();
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(psbt.Network.NetworkSet.CryptoCode);
|
||||
var handlers = parent.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
var settings = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||
TestLogs.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
|
||||
if (expectedError is null && !senderError)
|
||||
{
|
||||
|
@ -491,7 +488,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public class DummyStoreWebhookEvent : StoreWebhookEvent
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
public List<StoreWebhookEvent> WebhookEvents { get; set; } = new List<StoreWebhookEvent>();
|
||||
|
@ -576,9 +573,10 @@ retry:
|
|||
public async Task<uint256> PayOnChain(string invoiceId)
|
||||
{
|
||||
var cryptoCode = "BTC";
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
var client = await CreateClient();
|
||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
||||
var method = methods.First(m => m.PaymentMethod == cryptoCode);
|
||||
var method = methods.First(m => m.PaymentMethodId == pmi.ToString());
|
||||
var address = method.Destination;
|
||||
var tx = await client.CreateOnChainTransaction(StoreId, cryptoCode, new CreateOnChainTransactionRequest()
|
||||
{
|
||||
|
@ -601,7 +599,7 @@ retry:
|
|||
var cryptoCode = "BTC";
|
||||
var client = await CreateClient();
|
||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
||||
var method = methods.First(m => m.PaymentMethod == $"{cryptoCode}-LightningNetwork");
|
||||
var method = methods.First(m => m.PaymentMethodId == $"{cryptoCode}-LN");
|
||||
var bolt11 = method.Destination;
|
||||
TestLogs.LogInformation("PAYING");
|
||||
await parent.CustomerLightningD.Pay(bolt11);
|
||||
|
@ -615,7 +613,7 @@ retry:
|
|||
var network = SupportedNetwork.NBitcoinNetwork;
|
||||
var client = await CreateClient();
|
||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
||||
var method = methods.First(m => m.PaymentMethod == $"{cryptoCode}-LNURLPAY");
|
||||
var method = methods.First(m => m.PaymentMethodId == $"{cryptoCode}-LNURL");
|
||||
var lnurL = LNURL.LNURL.Parse(method.PaymentLink, out var tag);
|
||||
var http = new HttpClient();
|
||||
var payreq = (LNURL.LNURLPayRequest)await LNURL.LNURL.FetchInformation(lnurL, tag, http);
|
||||
|
@ -670,15 +668,36 @@ retry:
|
|||
var dbContext = this.parent.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
var db = (NpgsqlConnection)dbContext.Database.GetDbConnection();
|
||||
await db.OpenAsync();
|
||||
bool isHeader = true;
|
||||
using (var writer = db.BeginTextImport("COPY \"Invoices\" (\"Id\",\"Blob\",\"Created\",\"CustomerEmail\",\"ExceptionStatus\",\"ItemCode\",\"OrderId\",\"Status\",\"StoreDataId\",\"Archived\",\"Blob2\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||
{
|
||||
foreach (var invoice in oldInvoices)
|
||||
{
|
||||
var localInvoice = invoice.Replace("3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd", storeId);
|
||||
await writer.WriteLineAsync(localInvoice);
|
||||
if (isHeader)
|
||||
{
|
||||
isHeader = false;
|
||||
await writer.WriteLineAsync(invoice);
|
||||
}
|
||||
else
|
||||
{
|
||||
var localInvoice = invoice.Replace("3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd", storeId);
|
||||
var fields = localInvoice.Split(',');
|
||||
var blob1 = ZipUtils.Unzip(Encoders.Hex.DecodeData(fields[1].Substring(2)));
|
||||
var matched = Regex.Match(blob1, "xpub[^\\\"-]*");
|
||||
if (matched.Success)
|
||||
{
|
||||
var xpub = (BitcoinExtPubKey)Network.Main.Parse(matched.Value);
|
||||
var xpubTestnet = xpub.ExtPubKey.GetWif(Network.RegTest).ToString();
|
||||
blob1 = blob1.Replace(xpub.ToString(), xpubTestnet.ToString());
|
||||
fields[1] = $"\\x{Encoders.Hex.EncodeData(ZipUtils.Zip(blob1))}";
|
||||
localInvoice = string.Join(',', fields);
|
||||
}
|
||||
await writer.WriteLineAsync(localInvoice);
|
||||
}
|
||||
}
|
||||
await writer.FlushAsync();
|
||||
}
|
||||
isHeader = true;
|
||||
using (var writer = db.BeginTextImport("COPY \"Payments\" (\"Id\",\"Blob\",\"InvoiceDataId\",\"Accounted\",\"Blob2\",\"Type\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||
{
|
||||
foreach (var invoice in oldPayments)
|
||||
|
|
735
BTCPayServer.Tests/TestData/InvoiceMigrationTestVectors.json
Normal file
735
BTCPayServer.Tests/TestData/InvoiceMigrationTestVectors.json
Normal file
|
@ -0,0 +1,735 @@
|
|||
[
|
||||
{
|
||||
"type": "invoice",
|
||||
"input": {
|
||||
"id": "HuzCsv9hghew2FD6zVqUyY",
|
||||
"storeId": "3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd",
|
||||
"orderId": "CustomOrderId",
|
||||
"speedPolicy": 0,
|
||||
"rate": 5672.82929871779,
|
||||
"invoiceTime": 1538395793,
|
||||
"expirationTime": 1538396693,
|
||||
"depositAddress": "39mWwvUDoZ5CxXa6CmgaUda19qYj9LpQD1",
|
||||
"productInformation": {
|
||||
"itemDesc": "Masternode Staking",
|
||||
"itemCode": null,
|
||||
"physical": false,
|
||||
"price": 100.0,
|
||||
"currency": "EUR",
|
||||
"taxIncluded": 0.0
|
||||
},
|
||||
"buyerInformation": {
|
||||
"buyerName": null,
|
||||
"buyerEmail": "customer@example.com",
|
||||
"buyerCountry": null,
|
||||
"buyerZip": null,
|
||||
"buyerState": null,
|
||||
"buyerCity": null,
|
||||
"buyerAddress2": null,
|
||||
"buyerAddress1": null,
|
||||
"buyerPhone": null
|
||||
},
|
||||
"posData": null,
|
||||
"derivationStrategy": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR-[p2sh]",
|
||||
"derivationStrategies": "{\"BTC\":\"xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR-[p2sh]\"}",
|
||||
"status": "new",
|
||||
"exceptionStatus": null,
|
||||
"payments": [],
|
||||
"refundable": false,
|
||||
"refundMail": "customer@example.com",
|
||||
"redirectURL": "https://example.com/thanksyou",
|
||||
"txFee": 100,
|
||||
"fullNotifications": true,
|
||||
"notificationURL": "https://example.com/callbacks",
|
||||
"serverUrl": "https://mainnet.demo.btcpayserver.org",
|
||||
"cryptoData": {
|
||||
"BTC": {
|
||||
"rate": 5672.82929871779,
|
||||
"paymentMethod": {},
|
||||
"feeRate": 1,
|
||||
"txFee": 100,
|
||||
"depositAddress": "39mWwvUDoZ5CxXa6CmgaUda19qYj9LpQD1"
|
||||
}
|
||||
},
|
||||
"monitoringExpiration": 1538400293,
|
||||
"historicalAddresses": null,
|
||||
"availableAddressHashes": null,
|
||||
"extendedNotifications": false,
|
||||
"events": null,
|
||||
"paymentTolerance": 1.0
|
||||
},
|
||||
"expected": {
|
||||
"version": 3,
|
||||
"metadata": {
|
||||
"orderId": "CustomOrderId",
|
||||
"itemDesc": "Masternode Staking",
|
||||
"physical": false,
|
||||
"buyerEmail": "customer@example.com",
|
||||
"taxIncluded": 0.0
|
||||
},
|
||||
"rates": { "BTC": "5672.82929871779" },
|
||||
"serverUrl": "https://mainnet.demo.btcpayserver.org",
|
||||
"prompts": {
|
||||
"BTC-CHAIN": {
|
||||
"currency": "BTC",
|
||||
"paymentMethodFee": "0.000001",
|
||||
"details": {
|
||||
"paymentMethodFeeRate": 1,
|
||||
"recommendedFeeRate": 1,
|
||||
"accountDerivation": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR-[p2sh]"
|
||||
},
|
||||
"divisibility": 8,
|
||||
"destination": "39mWwvUDoZ5CxXa6CmgaUda19qYj9LpQD1"
|
||||
}
|
||||
},
|
||||
"invoiceTime": 1538395793,
|
||||
"redirectURL": "https://example.com/thanksyou",
|
||||
"receiptOptions": {},
|
||||
"internalTags": [],
|
||||
"expirationTime": 1538396693,
|
||||
"notificationURL": "https://example.com/callbacks",
|
||||
"paymentTolerance": 1.0,
|
||||
"fullNotifications": true,
|
||||
"monitoringExpiration": 1538400293
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "invoice",
|
||||
"input": {
|
||||
"id": "ANjq7kkV4mqDRPr5F8EmLZ",
|
||||
"rate": "66823.066",
|
||||
"price": "30",
|
||||
"txFee": 0,
|
||||
"events": null,
|
||||
"status": "new",
|
||||
"refunds": null,
|
||||
"storeId": "8Ja5pfPydLZTt3YLWhZuZ5vfXT2bNfvk1krUhF8ykCyt",
|
||||
"version": 2,
|
||||
"customerEmail": "toto@toto.com",
|
||||
"archived": false,
|
||||
"currency": "USD",
|
||||
"metadata": {
|
||||
"orderId": "CC",
|
||||
"itemDesc": "CC"
|
||||
},
|
||||
"payments": [],
|
||||
"serverUrl": "https://mainnet.demo.btcpayserver.org",
|
||||
"cryptoData": {
|
||||
"BTC": {
|
||||
"rate": 66823.066,
|
||||
"txFee": 0,
|
||||
"feeRate": 15.13,
|
||||
"paymentMethod": {
|
||||
"keyPath": "0/25903",
|
||||
"activated": true,
|
||||
"networkFeeRate": 12.319,
|
||||
"payjoinEnabled": false
|
||||
},
|
||||
"depositAddress": "bc1quf9l42tnma7zws9vusrvun4wchau0ue4zj838g"
|
||||
}
|
||||
},
|
||||
"paidAmount": {
|
||||
"net": 0.0,
|
||||
"gross": 0.0,
|
||||
"currency": "USD"
|
||||
},
|
||||
"refundMail": null,
|
||||
"invoiceTime": 1709806449,
|
||||
"isUnderPaid": true,
|
||||
"redirectURL": "https://test/",
|
||||
"speedPolicy": 1,
|
||||
"checkoutType": null,
|
||||
"internalTags": [],
|
||||
"depositAddress": "bc1quf9l42tnma7zws9vusrvun4wchau0ue4zj838g",
|
||||
"expirationTime": 1709808249,
|
||||
"receiptOptions": {
|
||||
"showQR": null,
|
||||
"enabled": null,
|
||||
"showPayments": null
|
||||
},
|
||||
"defaultLanguage": null,
|
||||
"exceptionStatus": "",
|
||||
"notificationURL": null,
|
||||
"storeSupportUrl": null,
|
||||
"paymentTolerance": 0.0,
|
||||
"fullNotifications": true,
|
||||
"notificationEmail": null,
|
||||
"lazyPaymentMethods": false,
|
||||
"requiresRefundEmail": null,
|
||||
"defaultPaymentMethod": "BTC",
|
||||
"derivationStrategies": {
|
||||
"BTC": {
|
||||
"label": null,
|
||||
"source": "ManualDerivationScheme",
|
||||
"signingKey": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR-[p2sh]",
|
||||
"isHotWallet": false,
|
||||
"accountOriginal": null,
|
||||
"accountDerivation": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR-[p2sh]",
|
||||
"accountKeySettings": [
|
||||
{
|
||||
"accountKey": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR-[p2sh]",
|
||||
"accountKeyPath": "84/0'/0'",
|
||||
"rootFingerprint": "312e13db"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"monitoringExpiration": 1709894649,
|
||||
"extendedNotifications": false,
|
||||
"redirectAutomatically": false,
|
||||
"availableAddressHashes": null
|
||||
},
|
||||
"expected": {
|
||||
"version": 3,
|
||||
"metadata": {
|
||||
"orderId": "CC",
|
||||
"itemDesc": "CC",
|
||||
"buyerEmail": "toto@toto.com"
|
||||
},
|
||||
"serverUrl": "https://mainnet.demo.btcpayserver.org",
|
||||
"rates": { "BTC": "66823.066" },
|
||||
"prompts": {
|
||||
"BTC-CHAIN": {
|
||||
"currency": "BTC",
|
||||
"destination": "bc1quf9l42tnma7zws9vusrvun4wchau0ue4zj838g",
|
||||
"details": {
|
||||
"recommendedFeeRate": 15.13,
|
||||
"paymentMethodFeeRate": 12.319,
|
||||
"keyPath": "0/25903",
|
||||
"accountDerivation": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR-[p2sh]"
|
||||
},
|
||||
"divisibility": 8
|
||||
}
|
||||
},
|
||||
"receiptOptions": {},
|
||||
"invoiceTime": 1709806449,
|
||||
"redirectURL": "https://test/",
|
||||
"speedPolicy": 1,
|
||||
"internalTags": [],
|
||||
"expirationTime": 1709808249,
|
||||
"fullNotifications": true,
|
||||
"defaultPaymentMethod": "BTC-CHAIN",
|
||||
"monitoringExpiration": 1709894649
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "invoice",
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"price": 0.0,
|
||||
"cryptoData": {
|
||||
"BTC": {
|
||||
"feeRate": 10.0,
|
||||
"paymentMethod": {
|
||||
},
|
||||
"depositAddress": "bc1q9l42tnma7zws9vusrvun4wchau0ue4zj838g"
|
||||
}
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"internalTags": [],
|
||||
"metadata": {},
|
||||
"version": 3,
|
||||
"prompts": {
|
||||
"BTC-CHAIN": {
|
||||
"currency": "BTC",
|
||||
"details": {
|
||||
"paymentMethodFeeRate": 10.0,
|
||||
"recommendedFeeRate": 10.0
|
||||
},
|
||||
"divisibility": 8,
|
||||
"destination": "bc1q9l42tnma7zws9vusrvun4wchau0ue4zj838g"
|
||||
}
|
||||
},
|
||||
"receiptOptions": {}
|
||||
},
|
||||
"skipRountripTest" : true
|
||||
},
|
||||
{
|
||||
"type": "invoice",
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"price": 0.0,
|
||||
"cryptoData": {
|
||||
"BTC": {
|
||||
"feeRate": 4.0,
|
||||
"paymentMethod": {
|
||||
"networkFeeMode": 2
|
||||
},
|
||||
"depositAddress": "bc1q9l42tnma7zws9vusrvun4wchau0ue4zj838g"
|
||||
}
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"internalTags": [],
|
||||
"metadata": {},
|
||||
"version": 3,
|
||||
"prompts": {
|
||||
"BTC-CHAIN": {
|
||||
"currency": "BTC",
|
||||
"details": {
|
||||
"feeMode": "Never",
|
||||
"recommendedFeeRate": 4.0
|
||||
},
|
||||
"divisibility": 8,
|
||||
"destination": "bc1q9l42tnma7zws9vusrvun4wchau0ue4zj838g"
|
||||
}
|
||||
},
|
||||
"receiptOptions": {}
|
||||
},
|
||||
"skipRountripTest": true
|
||||
},
|
||||
{
|
||||
"type": "invoice",
|
||||
"input": {
|
||||
"id": "AmoigMwzbaBNNCfo21yns1",
|
||||
"rate": "67056.018",
|
||||
"price": "13",
|
||||
"txFee": null,
|
||||
"events": null,
|
||||
"status": "new",
|
||||
"refunds": null,
|
||||
"storeId": "2b4H99crZ4JuPiRQwUuYpLQsjtHWASLVWHHkQQ2moiED",
|
||||
"version": 2,
|
||||
"archived": false,
|
||||
"currency": "USD",
|
||||
"metadata": {},
|
||||
"payments": [],
|
||||
"serverUrl": "https://mainnet.demo.btcpayserver.org",
|
||||
"cryptoData": {
|
||||
"BTC": {
|
||||
"rate": 67056.018,
|
||||
"txFee": null,
|
||||
"feeRate": null,
|
||||
"paymentMethod": {
|
||||
"keyPath": null,
|
||||
"activated": false,
|
||||
"networkFeeMode": 0,
|
||||
"networkFeeRate": null,
|
||||
"payjoinEnabled": false
|
||||
},
|
||||
"depositAddress": null
|
||||
},
|
||||
"BTC_LNURLPAY": {
|
||||
"rate": 67056.018,
|
||||
"txFee": null,
|
||||
"feeRate": null,
|
||||
"paymentMethod": {
|
||||
"BOLT11": null,
|
||||
"NodeInfo": "03d2a44997a0fb6deee0a31c389d9d6bcb6f929f1dd0ba67201d195f2b3c76087c@170.75.160.16:9735",
|
||||
"Preimage": {},
|
||||
"Activated": true,
|
||||
"InvoiceId": null,
|
||||
"Bech32Mode": true,
|
||||
"PayRequest": null,
|
||||
"PaymentHash": {},
|
||||
"ProvidedComment": null,
|
||||
"GeneratedBoltAmount": null,
|
||||
"ConsumedLightningAddress": null,
|
||||
"LightningSupportedPaymentMethod": {
|
||||
"CryptoCode": "BTC",
|
||||
"InternalNodeRef": "Internal Node"
|
||||
}
|
||||
},
|
||||
"depositAddress": null
|
||||
},
|
||||
"BTC_LightningLike": {
|
||||
"rate": 67056.018,
|
||||
"txFee": null,
|
||||
"feeRate": null,
|
||||
"paymentMethod": {
|
||||
"BOLT11": null,
|
||||
"NodeInfo": null,
|
||||
"Preimage": null,
|
||||
"Activated": false,
|
||||
"InvoiceId": null,
|
||||
"PaymentHash": null
|
||||
},
|
||||
"depositAddress": null
|
||||
}
|
||||
},
|
||||
"paidAmount": {
|
||||
"net": 0.0,
|
||||
"gross": 0.0,
|
||||
"currency": "USD"
|
||||
},
|
||||
"refundMail": null,
|
||||
"invoiceTime": 1709864059,
|
||||
"isUnderPaid": true,
|
||||
"redirectURL": null,
|
||||
"speedPolicy": 1,
|
||||
"checkoutType": null,
|
||||
"internalTags": [],
|
||||
"depositAddress": null,
|
||||
"expirationTime": 1709864959,
|
||||
"receiptOptions": {
|
||||
"showQR": null,
|
||||
"enabled": null,
|
||||
"showPayments": null
|
||||
},
|
||||
"defaultLanguage": null,
|
||||
"exceptionStatus": "",
|
||||
"notificationURL": null,
|
||||
"storeSupportUrl": null,
|
||||
"paymentTolerance": 0.0,
|
||||
"fullNotifications": true,
|
||||
"notificationEmail": null,
|
||||
"lazyPaymentMethods": true,
|
||||
"requiresRefundEmail": false,
|
||||
"defaultPaymentMethod": "BTC",
|
||||
"derivationStrategies": {
|
||||
"BTC": {
|
||||
"label": null,
|
||||
"source": "NBXplorerGenerated",
|
||||
"signingKey": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR",
|
||||
"isHotWallet": true,
|
||||
"accountOriginal": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR",
|
||||
"accountDerivation": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR",
|
||||
"accountKeySettings": [
|
||||
{
|
||||
"accountKey": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR",
|
||||
"accountKeyPath": "84'/0'/0'",
|
||||
"rootFingerprint": "312e13db"
|
||||
}
|
||||
]
|
||||
},
|
||||
"BTC_LNURLPAY": {
|
||||
"CryptoCode": "BTC",
|
||||
"LUD12Enabled": false,
|
||||
"UseBech32Scheme": true
|
||||
},
|
||||
"BTC_LightningLike": {
|
||||
"CryptoCode": "BTC",
|
||||
"InternalNodeRef": "Internal Node"
|
||||
}
|
||||
},
|
||||
"monitoringExpiration": 1709951359,
|
||||
"extendedNotifications": false,
|
||||
"redirectAutomatically": false,
|
||||
"availableAddressHashes": null
|
||||
},
|
||||
"expected": {
|
||||
"version": 3,
|
||||
"metadata": {},
|
||||
"serverUrl": "https://mainnet.demo.btcpayserver.org",
|
||||
"rates": { "BTC": "67056.018" },
|
||||
"prompts": {
|
||||
"BTC-CHAIN": {
|
||||
"inactive": true,
|
||||
"currency": "BTC",
|
||||
"details": {
|
||||
"accountDerivation": "xpub6DAbghfuRCW6Qqr3KfSDjik4kEUivAkfhcGHMkVEe4bZq53VQz79k44EoQ5MDpLJKKAPK5vVV6Px7U1R39eQibnbhDsW9uEQ1Kdh9CDbmgR"
|
||||
},
|
||||
"divisibility": 8
|
||||
},
|
||||
"BTC-LNURL": {
|
||||
"currency": "BTC",
|
||||
"divisibility": 11,
|
||||
"details": {
|
||||
"nodeInfo": "03d2a44997a0fb6deee0a31c389d9d6bcb6f929f1dd0ba67201d195f2b3c76087c@170.75.160.16:9735",
|
||||
"bech32Mode": true
|
||||
}
|
||||
},
|
||||
"BTC-LN": {
|
||||
"currency": "BTC",
|
||||
"divisibility": 11,
|
||||
"inactive": true,
|
||||
"details": {}
|
||||
}
|
||||
},
|
||||
"invoiceTime": 1709864059,
|
||||
"speedPolicy": 1,
|
||||
"expirationTime": 1709864959,
|
||||
"receiptOptions": {},
|
||||
"fullNotifications": true,
|
||||
"lazyPaymentMethods": true,
|
||||
"defaultPaymentMethod": "BTC-CHAIN",
|
||||
"internalTags": [],
|
||||
"monitoringExpiration": 1709951359
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "invoice",
|
||||
"input": {
|
||||
"id": "ULzMvaSEpvV4XxGb6F78LZ",
|
||||
"rate": "0",
|
||||
"type": "TopUp",
|
||||
"price": "0.0",
|
||||
"txFee": null,
|
||||
"events": null,
|
||||
"status": "new",
|
||||
"refunds": null,
|
||||
"storeId": "EBeAyDVUwBSNa6bdZNrytay9ARNL5UB9xnvNPh5xdnhy",
|
||||
"version": 2,
|
||||
"archived": false,
|
||||
"currency": "USD",
|
||||
"metadata": {
|
||||
},
|
||||
"payments": [
|
||||
],
|
||||
"serverUrl": "https://donate.nicolas-dorier.com",
|
||||
"cryptoData": {
|
||||
"BTC_LNURLPAY": {
|
||||
"rate": 29501.4,
|
||||
"txFee": null,
|
||||
"feeRate": null,
|
||||
"paymentMethod": {
|
||||
"BOLT11": "lnbc10n1pjn9nmzpp5a7znt4tv9gy5v6342xrgnntltkljffp255dph40vaf6964j27pvqhp59ly2g7flsy97vqahh9yue8qz7u6tvlpjfh9r0m9nzfezhm6fgmqscqzzsxqzursp55l9zht4zya3jyjdr9khr22z6afvjqdcw06l7vyd6tksdtsc8ezqs9qyyssqwswp9dnz9txv8t8zjrrts9rv4agu40ufqc04434f6lszdwvlhjk45m3pdcpqzghswkrcvgeaztcr6h82xp35suu64hnk4ms929pcahgpfg7sza",
|
||||
"NodeInfo": "03d2a44997a0fb6deee0a31c389d9d6bcb6f929f1dd0ba67201d195f2b3c76087c@170.75.160.16:9735",
|
||||
"Preimage": null,
|
||||
"Activated": true,
|
||||
"InvoiceId": "ef8535d56c2a09466a35518689cd7f5dbf24a42aa51a1bd5ecea745d564af058",
|
||||
"Bech32Mode": true,
|
||||
"PayRequest": {
|
||||
"tag": "payRequest",
|
||||
"callback": "https://donate.nicolas-dorier.com/BTC/UILNURL/pay/i/ULzMvaSEpvV4XxGb6F78LZ",
|
||||
"metadata": "[[\"text/identifier\",\"donate@donate.nicolas-dorier.com\"],[\"text/plain\",\"Paid to Nicolas Donation Store (Order ID: )\"]]",
|
||||
"maxSendable": 612000000000,
|
||||
"minSendable": 1000,
|
||||
"commentAllowed": 0
|
||||
},
|
||||
"PaymentHash": "ef8535d56c2a09466a35518689cd7f5dbf24a42aa51a1bd5ecea745d564af058",
|
||||
"ProvidedComment": null,
|
||||
"GeneratedBoltAmount": "1000",
|
||||
"ConsumedLightningAddress": "donate@donate.nicolas-dorier.com",
|
||||
"LightningSupportedPaymentMethod": {
|
||||
"CryptoCode": "BTC",
|
||||
"InternalNodeRef": "Internal Node"
|
||||
}
|
||||
},
|
||||
"depositAddress": null
|
||||
}
|
||||
},
|
||||
"paidAmount": {
|
||||
"net": 0.0,
|
||||
"gross": 0.0,
|
||||
"currency": "USD"
|
||||
},
|
||||
"refundMail": null,
|
||||
"invoiceTime": 1697828706,
|
||||
"isUnderPaid": false,
|
||||
"redirectURL": null,
|
||||
"speedPolicy": 1,
|
||||
"checkoutType": null,
|
||||
"internalTags": [
|
||||
],
|
||||
"depositAddress": null,
|
||||
"expirationTime": 1697829606,
|
||||
"receiptOptions": {
|
||||
"showQR": null,
|
||||
"enabled": null,
|
||||
"showPayments": null
|
||||
},
|
||||
"defaultLanguage": null,
|
||||
"exceptionStatus": "",
|
||||
"notificationURL": null,
|
||||
"storeSupportUrl": null,
|
||||
"paymentTolerance": 0.0,
|
||||
"fullNotifications": false,
|
||||
"notificationEmail": null,
|
||||
"lazyPaymentMethods": false,
|
||||
"requiresRefundEmail": null,
|
||||
"defaultPaymentMethod": "BTC",
|
||||
"derivationStrategies": {
|
||||
"BTC_LNURLPAY": {
|
||||
"CryptoCode": "BTC",
|
||||
"LUD12Enabled": false,
|
||||
"UseBech32Scheme": true
|
||||
}
|
||||
},
|
||||
"monitoringExpiration": 1697833206,
|
||||
"extendedNotifications": false,
|
||||
"redirectAutomatically": false,
|
||||
"availableAddressHashes": null
|
||||
},
|
||||
"expected": {
|
||||
"type": "TopUp",
|
||||
"version": 3,
|
||||
"rates": {
|
||||
"BTC": "29501.4"
|
||||
},
|
||||
"metadata": {
|
||||
},
|
||||
"serverUrl": "https://donate.nicolas-dorier.com",
|
||||
"prompts": {
|
||||
"BTC-LNURL": {
|
||||
"currency": "BTC",
|
||||
"divisibility": 11,
|
||||
"destination": "lnbc10n1pjn9nmzpp5a7znt4tv9gy5v6342xrgnntltkljffp255dph40vaf6964j27pvqhp59ly2g7flsy97vqahh9yue8qz7u6tvlpjfh9r0m9nzfezhm6fgmqscqzzsxqzursp55l9zht4zya3jyjdr9khr22z6afvjqdcw06l7vyd6tksdtsc8ezqs9qyyssqwswp9dnz9txv8t8zjrrts9rv4agu40ufqc04434f6lszdwvlhjk45m3pdcpqzghswkrcvgeaztcr6h82xp35suu64hnk4ms929pcahgpfg7sza",
|
||||
"details": {
|
||||
"nodeInfo": "03d2a44997a0fb6deee0a31c389d9d6bcb6f929f1dd0ba67201d195f2b3c76087c@170.75.160.16:9735",
|
||||
"invoiceId": "ef8535d56c2a09466a35518689cd7f5dbf24a42aa51a1bd5ecea745d564af058",
|
||||
"bech32Mode": true,
|
||||
"payRequest": {
|
||||
"tag": "payRequest",
|
||||
"callback": "https://donate.nicolas-dorier.com/BTC/UILNURL/pay/i/ULzMvaSEpvV4XxGb6F78LZ",
|
||||
"metadata": "[[\"text/identifier\",\"donate@donate.nicolas-dorier.com\"],[\"text/plain\",\"Paid to Nicolas Donation Store (Order ID: )\"]]",
|
||||
"maxSendable": 612000000000,
|
||||
"minSendable": 1000,
|
||||
"commentAllowed": 0
|
||||
},
|
||||
"paymentHash": "ef8535d56c2a09466a35518689cd7f5dbf24a42aa51a1bd5ecea745d564af058",
|
||||
"generatedBoltAmount": "1000",
|
||||
"consumedLightningAddress": "donate@donate.nicolas-dorier.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invoiceTime": 1697828706,
|
||||
"speedPolicy": 1,
|
||||
"internalTags": [],
|
||||
"expirationTime": 1697829606,
|
||||
"receiptOptions": {},
|
||||
"defaultPaymentMethod": "BTC-CHAIN",
|
||||
"monitoringExpiration": 1697833206
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "payment",
|
||||
"input": {
|
||||
"version": 1,
|
||||
"receivedTime": 1556044076,
|
||||
"networkFee": 0.000002,
|
||||
"outpoint": "552b74ff2fc2c8564de40c8cbefd8eb78f1bef5d3010009c643ff889e159663a3d000000",
|
||||
"output": "7a636f000000000017a914fd2a2d1d15eb6a490512953c12d6db0de662741d87",
|
||||
"accounted": true,
|
||||
"cryptoCode": "BTC",
|
||||
"cryptoPaymentData": "{\"ConfirmationCount\":1414,\"RBF\":false,\"NetworkFee\":0.0,\"Legacy\":false}",
|
||||
"cryptoPaymentDataType": "BTCLike"
|
||||
},
|
||||
"expected": {
|
||||
"version": 2,
|
||||
"destination": "3QmdQXq3tSMqiuwNy2ZcbFEHxZ5De4SBYJ",
|
||||
"paymentMethodFee": "0.000002",
|
||||
"divisibility": 8,
|
||||
"details": {
|
||||
"confirmationCount": 1414,
|
||||
"outpoint": "3a6659e189f83f649c0010305def1b8fb78efdbe8c0ce44d56c8c22fff742b55-61"
|
||||
}
|
||||
},
|
||||
"expectedProperties": {
|
||||
"Created": "04/23/2019 18:27:56 +00:00",
|
||||
"Type": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Amount": "0.07299962",
|
||||
"Accounted": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "payment",
|
||||
"input": {
|
||||
"receivedTime": 1538403202,
|
||||
"outpoint": "3211391d9dd2d01c8d9f164d0231f72a166c9b27b15fb2603f58a193ca47bea800000000",
|
||||
"output": "c74500000000000017a9145d741911858531a4e0a8b6a58bf5036d7f68857587",
|
||||
"accounted": true,
|
||||
"cryptoCode": "BTC",
|
||||
"cryptoPaymentData": "{\"ConfirmationCount\":19,\"RBF\":true,\"Legacy\":false}",
|
||||
"cryptoPaymentDataType": "BTCLike"
|
||||
},
|
||||
"expected": {
|
||||
"destination": "3AD9r1UyXXNFA2o3cucBwqX58xNaXvrdsv",
|
||||
"divisibility": 8,
|
||||
"details": {
|
||||
"confirmationCount": 19,
|
||||
"rbf": true,
|
||||
"outpoint": "a8be47ca93a1583f60b25fb1279b6c162af731024d169f8d1cd0d29d1d391132-0"
|
||||
},
|
||||
"version": 2
|
||||
},
|
||||
"expectedProperties": {
|
||||
"Created": "10/01/2018 14:13:22 +00:00",
|
||||
"Type": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Amount": "0.00017863",
|
||||
"Accounted": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "payment",
|
||||
"input": {
|
||||
"output": null,
|
||||
"version": 1,
|
||||
"outpoint": null,
|
||||
"accounted": true,
|
||||
"cryptoCode": "BTC",
|
||||
"networkFee": 0.0,
|
||||
"receivedTimeMs": 1711005875969,
|
||||
"cryptoPaymentData": "{\"amount\":1000,\"bolT11\":\"lnbc10n1pjlhc90pp5smwdey0c5skr758lpm0w7tf56cafmdd0hey2nylwfl9q398jgf5shp5qra0y2q5w98at2vv0upux3sn2p0efrxs4h2nyzghqcj7009nqpsqcqzzsxqyz5vqsp5y8fvj3dxnwdaavx89nc5vykuywcmzdefh7x7z62q873hmawh7pws9qyyssq8f7ptu037y2vcclwst6nj8cy8ndhrcxj729ea4ntwdmpgtrwfqun5jnh4zul9s7vvtqd58wurdzk9e3xpzs3ykm0umjdwcttfussaxqqayadfs\",\"paymentHash\":\"86dcdc91f8a42c3f50ff0edeef2d34d63a9db5afbe48a993ee4fca0894f24269\",\"paymentType\":\"LNURLPAY\",\"networkFee\":0.0}",
|
||||
"cryptoPaymentDataType": "LNURLPAY"
|
||||
},
|
||||
"expected": {
|
||||
"version": 2,
|
||||
"divisibility": 11,
|
||||
"destination": "lnbc10n1pjlhc90pp5smwdey0c5skr758lpm0w7tf56cafmdd0hey2nylwfl9q398jgf5shp5qra0y2q5w98at2vv0upux3sn2p0efrxs4h2nyzghqcj7009nqpsqcqzzsxqyz5vqsp5y8fvj3dxnwdaavx89nc5vykuywcmzdefh7x7z62q873hmawh7pws9qyyssq8f7ptu037y2vcclwst6nj8cy8ndhrcxj729ea4ntwdmpgtrwfqun5jnh4zul9s7vvtqd58wurdzk9e3xpzs3ykm0umjdwcttfussaxqqayadfs",
|
||||
"details": {
|
||||
"paymentHash": "86dcdc91f8a42c3f50ff0edeef2d34d63a9db5afbe48a993ee4fca0894f24269"
|
||||
}
|
||||
},
|
||||
"expectedProperties": {
|
||||
"Created": "03/21/2024 07:24:35 +00:00",
|
||||
"CreatedInMs": "1711005875969",
|
||||
"Amount": "0.00000001",
|
||||
"Type": "BTC-LNURL",
|
||||
"Currency": "BTC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "payment",
|
||||
"input": {
|
||||
"output": null,
|
||||
"version": 1,
|
||||
"outpoint": "2e0ee2cccec304926677621ab5c1c80723f695740f1aed6915b4e72995d8172016000000",
|
||||
"accounted": true,
|
||||
"cryptoCode": "BTC",
|
||||
"networkFee": 0.0,
|
||||
"receivedTimeMs": 1710974348741,
|
||||
"cryptoPaymentData": "{\"confirmationCount\":6,\"rbf\":false,\"address\":\"bc1qdamnd0fjegj4a5efrwx4gvjc69zufmu7ntf5ft\",\"keyPath\":\"0/708\",\"value\":197864,\"legacy\":false}",
|
||||
"cryptoPaymentDataType": "BTCLike"
|
||||
},
|
||||
"expected": {
|
||||
"destination": "bc1qdamnd0fjegj4a5efrwx4gvjc69zufmu7ntf5ft",
|
||||
"divisibility": 8,
|
||||
"details": {
|
||||
"confirmationCount": 6,
|
||||
"keyPath": "0/708",
|
||||
"outpoint": "2017d89529e7b41569ed1a0f7495f62307c8c1b51a6277669204c3cecce20e2e-22"
|
||||
},
|
||||
"version": 2
|
||||
},
|
||||
"expectedProperties": {
|
||||
"Created": "03/20/2024 22:39:08 +00:00",
|
||||
"CreatedInMs": "1710974348741",
|
||||
"Amount": "0.00197864",
|
||||
"Type": "BTC-CHAIN",
|
||||
"Currency": "BTC",
|
||||
"Status": "Settled",
|
||||
"Accounted": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "payment",
|
||||
"input": {
|
||||
"output": null,
|
||||
"version": 1,
|
||||
"outpoint": "2e0ee2cccec304926677621ab5c1c80723f695740f1aed6915b4e72995d8172016000000",
|
||||
"accounted": true,
|
||||
"cryptoCode": "BTC",
|
||||
"networkFee": 0.0,
|
||||
"receivedTimeMs": 1710974348741,
|
||||
"cryptoPaymentData": "{\"confirmationCount\":6,\"rbf\":true,\"address\":\"bc1qdamnd0fjegj4a5efrwx4gvjc69zufmu7ntf5ft\",\"keyPath\":\"0/708\",\"value\":197864,\"legacy\":false}",
|
||||
"cryptoPaymentDataType": "BTCLike"
|
||||
},
|
||||
"expected": {
|
||||
"divisibility": 8,
|
||||
"destination": "bc1qdamnd0fjegj4a5efrwx4gvjc69zufmu7ntf5ft",
|
||||
"details": {
|
||||
"confirmationCount": 6,
|
||||
"keyPath": "0/708",
|
||||
"outpoint": "2017d89529e7b41569ed1a0f7495f62307c8c1b51a6277669204c3cecce20e2e-22",
|
||||
"RBF": true
|
||||
},
|
||||
"version": 2
|
||||
}
|
||||
}
|
||||
]
|
|
@ -40,6 +40,7 @@ using BTCPayServer.Payments.PayJoin.Sender;
|
|||
using BTCPayServer.Plugins.PayButton;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
|
@ -69,6 +70,7 @@ using NBXplorer.Models;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Schema;
|
||||
using Npgsql;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
@ -377,14 +379,16 @@ namespace BTCPayServer.Tests
|
|||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
await user.ModifyWalletSettings(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
await user.ModifyOnchainPaymentSettings(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.0001m, "BTC"));
|
||||
await tester.WaitForEvent<InvoiceNewPaymentDetailsEvent>(async () =>
|
||||
{
|
||||
await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.00005m));
|
||||
}, e => e.InvoiceId == invoice.Id && e.PaymentMethodId.PaymentType == LightningPaymentType.Instance);
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.00005m), new NBitcoin.RPC.SendToAddressParameters()
|
||||
{
|
||||
Replaceable = false
|
||||
});
|
||||
}, e => e.InvoiceId == invoice.Id && e.PaymentMethodId == PaymentTypes.LN.GetPaymentMethodId("BTC"));
|
||||
Invoice newInvoice = null;
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
|
@ -560,7 +564,7 @@ namespace BTCPayServer.Tests
|
|||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess();
|
||||
acc.RegisterDerivationScheme("BTC");
|
||||
await acc.ModifyWalletSettings(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
await acc.ModifyOnchainPaymentSettings(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
var invoice = acc.BitPay.CreateInvoice(new Invoice
|
||||
{
|
||||
Price = 5.0m,
|
||||
|
@ -1043,16 +1047,17 @@ namespace BTCPayServer.Tests
|
|||
tx1.ToString(),
|
||||
}).Result["txid"].Value<string>());
|
||||
TestLogs.LogInformation($"Bumped with {tx1Bump}");
|
||||
var handler = tester.PayTester.GetService<PaymentMethodHandlerDictionary>().GetBitcoinHandler("BTC");
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoiceEntity = await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id);
|
||||
var btcPayments = invoiceEntity.GetAllBitcoinPaymentData(false).ToArray();
|
||||
var btcPayments = invoiceEntity.GetAllBitcoinPaymentData(handler, false).ToArray();
|
||||
var payments = invoiceEntity.GetPayments(false).ToArray();
|
||||
Assert.Equal(tx1, btcPayments[0].Outpoint.Hash);
|
||||
Assert.False(payments[0].Accounted);
|
||||
Assert.Equal(tx1Bump, payments[1].Outpoint.Hash);
|
||||
Assert.Equal(tx1Bump, btcPayments[1].Outpoint.Hash);
|
||||
Assert.True(payments[1].Accounted);
|
||||
Assert.Equal(0.0m, payments[1].NetworkFee);
|
||||
Assert.Equal(0.0m, payments[1].PaymentMethodFee);
|
||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||
Assert.Equal(payment1, invoice.BtcPaid);
|
||||
Assert.Equal("paid", invoice.Status);
|
||||
|
@ -1085,8 +1090,8 @@ namespace BTCPayServer.Tests
|
|||
Assert.IsType<ViewResult>(await user.GetController<UIInvoiceController>().Invoice(invoice.Id)).Model)
|
||||
.Payments;
|
||||
Assert.Single(payments);
|
||||
var paymentData = payments.First().GetCryptoPaymentData() as BitcoinLikePaymentData;
|
||||
Assert.NotNull(paymentData.KeyPath);
|
||||
var paymentData = payments.First().Details;
|
||||
Assert.NotNull(paymentData["keyPath"]);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
|
@ -1339,12 +1344,10 @@ namespace BTCPayServer.Tests
|
|||
var btcmethod = (await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id))[0];
|
||||
var paid = btcSent;
|
||||
var invoiceAddress = BitcoinAddress.Create(btcmethod.Destination, cashCow.Network);
|
||||
var btc = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
||||
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var networkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
|
||||
.GetPaymentMethods()[btc]
|
||||
.GetPaymentMethodDetails()
|
||||
.AssertType<BitcoinLikeOnChainPaymentMethod>()
|
||||
.GetNextNetworkFee();
|
||||
.GetPaymentPrompt(btc)
|
||||
.PaymentMethodFee;
|
||||
if (networkFeeMode != NetworkFeeMode.Always)
|
||||
{
|
||||
networkFee = 0.0m;
|
||||
|
@ -1364,7 +1367,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal("False", bitpayinvoice.ExceptionStatus.ToString());
|
||||
|
||||
// Check if we index by price correctly once we know it
|
||||
var invoices = await client.GetInvoices(user.StoreId, textSearch: $"{bitpayinvoice.Price.ToString(CultureInfo.InvariantCulture)}");
|
||||
var invoices = await client.GetInvoices(user.StoreId, textSearch: bitpayinvoice.Price.ToString(CultureInfo.InvariantCulture).Split('.')[0]);
|
||||
Assert.Contains(invoices, inv => inv.Id == bitpayinvoice.Id);
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
|
@ -1492,15 +1495,15 @@ namespace BTCPayServer.Tests
|
|||
await user.RegisterLightningNodeAsync("BTC");
|
||||
|
||||
|
||||
var lnMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToString();
|
||||
var btcMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToString();
|
||||
var lnMethod = PaymentTypes.LN.GetPaymentMethodId("BTC").ToString();
|
||||
var btcMethod = PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString();
|
||||
|
||||
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
|
||||
var vm = Assert.IsType<CheckoutAppearanceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<UIStoresController>().CheckoutAppearance()).Model);
|
||||
Assert.Equal(2, vm.PaymentMethodCriteria.Count);
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria.Where(m => m.PaymentMethod == btcMethod.ToString()));
|
||||
Assert.Equal(new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
Assert.Equal(PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(), criteria.PaymentMethod);
|
||||
criteria.Value = "5 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm)
|
||||
|
@ -1518,8 +1521,8 @@ namespace BTCPayServer.Tests
|
|||
}, Facade.Merchant);
|
||||
// LN and LNURL
|
||||
Assert.Equal(2, invoice.CryptoInfo.Length);
|
||||
Assert.Contains(invoice.CryptoInfo, c => c.PaymentType == PaymentTypes.LNURLPay.ToString());
|
||||
Assert.Contains(invoice.CryptoInfo, c => c.PaymentType == PaymentTypes.LightningLike.ToString());
|
||||
Assert.Contains(invoice.CryptoInfo, c => c.PaymentType == "BTC-LNURL");
|
||||
Assert.Contains(invoice.CryptoInfo, c => c.PaymentType == "BTC-LN");
|
||||
|
||||
// Let's replicate https://github.com/btcpayserver/btcpayserver/issues/2963
|
||||
// We allow BTC for more than 5 USD, and LN for less than 150. The default is LN, so the default
|
||||
|
@ -1639,7 +1642,7 @@ namespace BTCPayServer.Tests
|
|||
user.SetLNUrl(cryptoCode, false);
|
||||
var vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode).ToString(), criteria.PaymentMethod);
|
||||
criteria.Value = "2 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.LessThan;
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm)
|
||||
|
@ -1652,14 +1655,14 @@ namespace BTCPayServer.Tests
|
|||
Currency = "USD"
|
||||
}, Facade.Merchant);
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
Assert.Equal("BTC-LN", invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
// Activating LNUrl, we should still have only 1 payment criteria that can be set.
|
||||
user.RegisterLightningNode(cryptoCode);
|
||||
user.SetLNUrl(cryptoCode, true);
|
||||
vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||
criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId(cryptoCode, LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode).ToString(), criteria.PaymentMethod);
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm).Result);
|
||||
|
||||
// However, creating an invoice should show LNURL
|
||||
|
@ -1713,7 +1716,7 @@ namespace BTCPayServer.Tests
|
|||
public async Task CanChangeNetworkFeeMode()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
var btc = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
||||
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
|
@ -1733,10 +1736,7 @@ namespace BTCPayServer.Tests
|
|||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
var nextNetworkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
|
||||
.GetPaymentMethods()[btc]
|
||||
.GetPaymentMethodDetails()
|
||||
.AssertType<BitcoinLikeOnChainPaymentMethod>()
|
||||
.GetNextNetworkFee();
|
||||
.GetPaymentPrompt(btc).PaymentMethodFee;
|
||||
var firstPaymentFee = nextNetworkFee;
|
||||
switch (networkFeeMode)
|
||||
{
|
||||
|
@ -1768,10 +1768,8 @@ namespace BTCPayServer.Tests
|
|||
TestLogs.LogInformation($"Remaining due after first payment: {due}");
|
||||
Assert.Equal(Money.Coins(firstPayment), Money.Parse(invoice.CryptoInfo[0].Paid));
|
||||
nextNetworkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
|
||||
.GetPaymentMethods()[btc]
|
||||
.GetPaymentMethodDetails()
|
||||
.AssertType<BitcoinLikeOnChainPaymentMethod>()
|
||||
.GetNextNetworkFee();
|
||||
.GetPaymentPrompt(btc)
|
||||
.PaymentMethodFee;
|
||||
switch (networkFeeMode)
|
||||
{
|
||||
case NetworkFeeMode.Never:
|
||||
|
@ -1942,10 +1940,10 @@ namespace BTCPayServer.Tests
|
|||
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
||||
var entity = (await repo.GetInvoice(invoice6.Id));
|
||||
Assert.Equal((decimal)ulong.MaxValue, entity.Price);
|
||||
entity.GetPaymentMethods().First().Calculate();
|
||||
entity.GetPaymentPrompts().First().Calculate();
|
||||
// Shouldn't be possible as we clamp the value, but existing invoice may have that
|
||||
entity.Price = decimal.MaxValue;
|
||||
entity.GetPaymentMethods().First().Calculate();
|
||||
entity.GetPaymentPrompts().First().Calculate();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1979,14 +1977,14 @@ namespace BTCPayServer.Tests
|
|||
});
|
||||
|
||||
var invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model =>
|
||||
PaymentMethodId.Parse(model.PaymentMethod) ==
|
||||
new PaymentMethodId("BTC", BitcoinPaymentType.Instance))
|
||||
PaymentMethodId.Parse(model.PaymentMethodId) ==
|
||||
PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||
.PaymentLink, tester.ExplorerNode.Network);
|
||||
var halfPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)/2m));
|
||||
|
||||
invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model =>
|
||||
PaymentMethodId.Parse(model.PaymentMethod) ==
|
||||
new PaymentMethodId("BTC", BitcoinPaymentType.Instance))
|
||||
PaymentMethodId.Parse(model.PaymentMethodId) ==
|
||||
PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||
.PaymentLink, tester.ExplorerNode.Network);
|
||||
var remainingPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)));
|
||||
|
||||
|
@ -2028,8 +2026,8 @@ namespace BTCPayServer.Tests
|
|||
Currency = "BTC",
|
||||
});
|
||||
invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model =>
|
||||
PaymentMethodId.Parse(model.PaymentMethod) ==
|
||||
new PaymentMethodId("BTC", BitcoinPaymentType.Instance))
|
||||
PaymentMethodId.Parse(model.PaymentMethodId) ==
|
||||
PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||
.PaymentLink, tester.ExplorerNode.Network);
|
||||
halfPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)/2m));
|
||||
|
||||
|
@ -2140,7 +2138,7 @@ namespace BTCPayServer.Tests
|
|||
var ctx = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
Assert.Equal(0, invoice.CryptoInfo[0].TxCount);
|
||||
Assert.True(invoice.MinerFees.ContainsKey("BTC"));
|
||||
Assert.Contains(invoice.MinerFees["BTC"].SatoshiPerBytes, new[] { 100.0m, 20.0m });
|
||||
Assert.Contains(Math.Round(invoice.MinerFees["BTC"].SatoshiPerBytes), new[] { 100.0m, 20.0m });
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
|
@ -2218,14 +2216,6 @@ namespace BTCPayServer.Tests
|
|||
|
||||
await cashCow.GenerateAsync(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("confirmed", localInvoice.Status);
|
||||
});
|
||||
|
||||
await cashCow.GenerateAsync(5); //Now should be complete
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
|
@ -2268,7 +2258,7 @@ namespace BTCPayServer.Tests
|
|||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("confirmed", localInvoice.Status);
|
||||
Assert.Equal("complete", localInvoice.Status);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
@ -2289,7 +2279,7 @@ namespace BTCPayServer.Tests
|
|||
c =>
|
||||
{
|
||||
Assert.False(c.AfterExpiration);
|
||||
Assert.Equal(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), c.PaymentMethod);
|
||||
Assert.Equal(PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(), c.PaymentMethodId);
|
||||
Assert.NotNull(c.Payment);
|
||||
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
||||
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
||||
|
@ -2299,7 +2289,7 @@ namespace BTCPayServer.Tests
|
|||
c =>
|
||||
{
|
||||
Assert.False(c.AfterExpiration);
|
||||
Assert.Equal(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), c.PaymentMethod);
|
||||
Assert.Equal(PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(), c.PaymentMethodId);
|
||||
Assert.NotNull(c.Payment);
|
||||
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
||||
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
||||
|
@ -2539,7 +2529,9 @@ namespace BTCPayServer.Tests
|
|||
await RestartMigration(tester);
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
Assert.True(string.IsNullOrEmpty(store.DerivationStrategy));
|
||||
var v = (DerivationSchemeSettings)store.GetSupportedPaymentMethods(tester.NetworkProvider).First();
|
||||
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var v = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||
Assert.Equal(derivation, v.AccountDerivation.ToString());
|
||||
Assert.Equal(derivation, v.AccountOriginal.ToString());
|
||||
Assert.Equal(xpub, v.SigningKey.ToString());
|
||||
|
@ -2547,13 +2539,26 @@ namespace BTCPayServer.Tests
|
|||
|
||||
await acc.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning, true);
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
var lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().First();
|
||||
|
||||
pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
|
||||
var lnMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, handlers);
|
||||
Assert.NotNull(lnMethod.GetExternalLightningUrl());
|
||||
var conf = store.GetPaymentMethodConfig(pmi);
|
||||
conf["LightningConnectionString"] = conf["connectionString"].Value<string>();
|
||||
conf["DisableBOLT11PaymentOption"] = true;
|
||||
((JObject)conf).Remove("connectionString");
|
||||
store.SetPaymentMethodConfig(pmi, conf);
|
||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||
await RestartMigration(tester);
|
||||
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().First();
|
||||
lnMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, handlers);
|
||||
Assert.Null(lnMethod.GetExternalLightningUrl());
|
||||
Assert.True(lnMethod.IsInternalNode);
|
||||
conf = store.GetPaymentMethodConfig(pmi);
|
||||
Assert.Null(conf["CryptoCode"]); // Osolete
|
||||
Assert.Null(conf["connectionString"]); // Null, so should be stripped
|
||||
Assert.Null(conf["DisableBOLT11PaymentOption"]); // Old garbage cleaned
|
||||
|
||||
// Test if legacy lightning charge settings are converted to LightningConnectionString
|
||||
store.DerivationStrategies = new JObject()
|
||||
|
@ -2569,9 +2574,8 @@ namespace BTCPayServer.Tests
|
|||
}.ToString();
|
||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||
await RestartMigration(tester);
|
||||
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().First();
|
||||
lnMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, handlers);
|
||||
Assert.NotNull(lnMethod.GetExternalLightningUrl());
|
||||
|
||||
var url = lnMethod.GetExternalLightningUrl();
|
||||
|
@ -2596,8 +2600,23 @@ namespace BTCPayServer.Tests
|
|||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||
await RestartMigration(tester);
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().First();
|
||||
lnMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, handlers);
|
||||
Assert.True(lnMethod.IsInternalNode);
|
||||
|
||||
store.SetPaymentMethodConfig(PaymentMethodId.Parse("BTC-LNURL"),
|
||||
new JObject()
|
||||
{
|
||||
["CryptoCode"] = "BTC",
|
||||
["LUD12Enabled"] = true,
|
||||
["UseBech32Scheme"] = false,
|
||||
});
|
||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||
await RestartMigration(tester);
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
conf = store.GetPaymentMethodConfig(PaymentMethodId.Parse("BTC-LNURL"));
|
||||
Assert.Null(conf["CryptoCode"]);
|
||||
Assert.True(conf["lud12Enabled"].Value<bool>());
|
||||
Assert.Null(conf["useBech32Scheme"]); // default stripped
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
|
@ -2725,7 +2744,7 @@ namespace BTCPayServer.Tests
|
|||
serializer.ToString(new Dictionary<string, string>()
|
||||
{
|
||||
{
|
||||
new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToString(),
|
||||
PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(),
|
||||
new KeyPath("44'/0'/0'").ToString()
|
||||
}
|
||||
})));
|
||||
|
@ -2756,13 +2775,33 @@ namespace BTCPayServer.Tests
|
|||
Assert.Empty(blob.AdditionalData);
|
||||
Assert.Single(blob.PaymentMethodCriteria);
|
||||
Assert.Contains(blob.PaymentMethodCriteria,
|
||||
criteria => criteria.PaymentMethod == new PaymentMethodId("BTC", BitcoinPaymentType.Instance) &&
|
||||
criteria => criteria.PaymentMethod == PaymentTypes.CHAIN.GetPaymentMethodId("BTC") &&
|
||||
criteria.Above && criteria.Value.Value == 5m && criteria.Value.Currency == "USD");
|
||||
Assert.Equal(NetworkFeeMode.Never, blob.NetworkFeeMode);
|
||||
Assert.Contains(store.GetSupportedPaymentMethods(tester.NetworkProvider), method =>
|
||||
method is DerivationSchemeSettings dss &&
|
||||
method.PaymentId == new PaymentMethodId("BTC", BitcoinPaymentType.Instance) &&
|
||||
dss.AccountKeyPath == new KeyPath("44'/0'/0'"));
|
||||
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||
Assert.Contains(store.GetPaymentMethodConfigs(handlers), method =>
|
||||
method.Value is DerivationSchemeSettings dss &&
|
||||
method.Key == PaymentTypes.CHAIN.GetPaymentMethodId("BTC") &&
|
||||
dss.AccountKeySettings[0].AccountKeyPath == new KeyPath("44'/0'/0'"));
|
||||
|
||||
await acc.ImportOldInvoices();
|
||||
var dbContext = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
var invoiceMigrator = tester.PayTester.GetService<InvoiceBlobMigratorHostedService>();
|
||||
invoiceMigrator.BatchSize = 2;
|
||||
await invoiceMigrator.ResetMigration();
|
||||
await invoiceMigrator.StartAsync(default);
|
||||
tester.DeleteStore = false;
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoices = await dbContext.Invoices.AsNoTracking().ToListAsync();
|
||||
foreach (var invoice in invoices)
|
||||
{
|
||||
Assert.NotNull(invoice.Currency);
|
||||
Assert.NotNull(invoice.Amount);
|
||||
Assert.NotNull(invoice.Blob2);
|
||||
}
|
||||
Assert.True(await invoiceMigrator.IsComplete());
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task RestartMigration(ServerTester tester)
|
||||
|
@ -3062,7 +3101,7 @@ namespace BTCPayServer.Tests
|
|||
// 1 payment on chain
|
||||
Assert.Equal(4, report.Data.Count);
|
||||
var lnAddressIndex = report.GetIndex("LightningAddress");
|
||||
var paymentTypeIndex = report.GetIndex("PaymentType");
|
||||
var paymentTypeIndex = report.GetIndex("Category");
|
||||
Assert.Contains(report.Data, d => d[lnAddressIndex]?.Value<string>()?.Contains(acc.LNAddress) is true);
|
||||
var paymentTypes = report.Data
|
||||
.GroupBy(d => d[paymentTypeIndex].Value<string>())
|
||||
|
|
|
@ -216,6 +216,5 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1misc_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1webhooks_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1invoices_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1misc_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores-payment-methods_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1users_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1webhooks_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
@using BTCPayServer.Payments
|
||||
@using BTCPayServer.Services.Invoices
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Components.InvoiceStatus.InvoiceStatusViewModel
|
||||
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
|
||||
@inject Dictionary<PaymentMethodId, IPaymentModelExtension> Extensions
|
||||
|
||||
@{
|
||||
var state = Model.State.ToString();
|
||||
|
@ -41,16 +42,17 @@
|
|||
</div>
|
||||
@if (Model.Payments != null)
|
||||
{
|
||||
foreach (var paymentMethodId in Model.Payments.Select(payment => payment.GetPaymentMethodId()).Distinct())
|
||||
foreach (var paymentMethodId in Model.Payments.Select(payment => payment.PaymentMethodId).Distinct())
|
||||
{
|
||||
var image = PaymentMethodHandlerDictionary[paymentMethodId]?.GetCryptoImage(paymentMethodId);
|
||||
var badge = paymentMethodId.PaymentType.GetBadge();
|
||||
var extension = Extensions.TryGetValue(paymentMethodId, out var e) ? e : null;
|
||||
var image = extension?.Image;
|
||||
var badge = extension?.Badge;
|
||||
if (!string.IsNullOrEmpty(image) || !string.IsNullOrEmpty(badge))
|
||||
{
|
||||
<span class="d-inline-flex align-items-center gap-1">
|
||||
@if (!string.IsNullOrEmpty(image))
|
||||
{
|
||||
<img src="@Context.Request.GetRelativePathOrAbsolute(image)" alt="@paymentMethodId.PaymentType.ToString()" style="height:1.5em" />
|
||||
<img src="@Context.Request.GetRelativePathOrAbsolute(image)" alt="@paymentMethodId.ToString()" style="height:1.5em" />
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(badge))
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
@inject PoliciesSettings PoliciesSettings
|
||||
@inject ThemeSettings Theme
|
||||
@inject PluginService PluginService
|
||||
@inject PrettyNameProvider PrettyName
|
||||
|
||||
@model BTCPayServer.Components.MainNav.MainNavViewModel
|
||||
|
||||
|
@ -60,14 +61,14 @@
|
|||
{
|
||||
<a asp-area="" asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@scheme.WalletId" class="nav-link @ViewData.IsActiveCategory(typeof(WalletsNavPages), scheme.WalletId.ToString()) @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Wallet{scheme.Crypto}")">
|
||||
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
|
||||
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} Wallet" : "Bitcoin")</span>
|
||||
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Wallet{scheme.Crypto}")">
|
||||
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
|
||||
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} Wallet" : "Bitcoin")</span>
|
||||
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
|
@ -80,14 +81,14 @@
|
|||
{
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="Lightning" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Lightning) @ViewData.IsActivePage(StoreNavPages.LightningSettings)" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
|
||||
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
|
||||
<span>@(Model.AltcoinsBuild ? $"{scheme.CryptoCode} " : "")Lightning</span>
|
||||
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-area="" asp-controller="UIStores" asp-action="SetupLightningNode" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.LightningSettings)" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
|
||||
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
|
||||
<span>@(Model.AltcoinsBuild ? $"{scheme.CryptoCode} " : "")Lightning</span>
|
||||
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ using BTCPayServer.Payments;
|
|||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -30,6 +31,7 @@ public class StoreLightningBalance : ViewComponent
|
|||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public StoreLightningBalance(
|
||||
StoreRepository storeRepo,
|
||||
|
@ -38,8 +40,9 @@ public class StoreLightningBalance : ViewComponent
|
|||
BTCPayServerOptions btcpayServerOptions,
|
||||
LightningClientFactoryService lightningClientFactory,
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
IOptions<ExternalServicesOptions> externalServiceOptions,
|
||||
IAuthorizationService authorizationService)
|
||||
IOptions<ExternalServicesOptions> externalServiceOptions,
|
||||
IAuthorizationService authorizationService,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_currencies = currencies;
|
||||
|
@ -47,6 +50,7 @@ public class StoreLightningBalance : ViewComponent
|
|||
_btcpayServerOptions = btcpayServerOptions;
|
||||
_externalServiceOptions = externalServiceOptions;
|
||||
_authorizationService = authorizationService;
|
||||
_handlers = handlers;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
}
|
||||
|
@ -101,10 +105,8 @@ public class StoreLightningBalance : ViewComponent
|
|||
private async Task<ILightningClient> GetLightningClient(StoreData store, string cryptoCode )
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_networkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
var id = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id, _handlers);
|
||||
if (existing == null)
|
||||
return null;
|
||||
|
||||
|
|
|
@ -8,7 +8,9 @@ using BTCPayServer.Abstractions.Extensions;
|
|||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
|
@ -27,21 +29,20 @@ public class StoreRecentTransactions : ViewComponent
|
|||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
private readonly LabelService _labelService;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||
|
||||
public BTCPayNetworkProvider NetworkProvider { get; }
|
||||
|
||||
public StoreRecentTransactions(
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
WalletRepository walletRepository,
|
||||
LabelService labelService,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
TransactionLinkProviders transactionLinkProviders)
|
||||
{
|
||||
NetworkProvider = networkProvider;
|
||||
_walletProvider = walletProvider;
|
||||
_walletRepository = walletRepository;
|
||||
_labelService = labelService;
|
||||
_handlers = handlers;
|
||||
_transactionLinkProviders = transactionLinkProviders;
|
||||
}
|
||||
|
||||
|
@ -57,15 +58,16 @@ public class StoreRecentTransactions : ViewComponent
|
|||
if (vm.InitialRendering)
|
||||
return View(vm);
|
||||
|
||||
var derivationSettings = vm.Store.GetDerivationSchemeSettings(NetworkProvider, vm.CryptoCode);
|
||||
var derivationSettings = vm.Store.GetDerivationSchemeSettings(_handlers, vm.CryptoCode);
|
||||
var transactions = new List<StoreRecentTransactionViewModel>();
|
||||
if (derivationSettings?.AccountDerivation is not null)
|
||||
{
|
||||
var network = derivationSettings.Network;
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(vm.CryptoCode);
|
||||
var network = ((IHasNetwork)_handlers[pmi]).Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0), cancellationToken: this.HttpContext.RequestAborted);
|
||||
var walletTransactionsInfo = await _walletRepository.GetWalletTransactionsInfo(vm.WalletId, allTransactions.Select(t => t.TransactionId.ToString()).ToArray());
|
||||
var pmi = new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike);
|
||||
|
||||
transactions = allTransactions
|
||||
.Select(tx =>
|
||||
{
|
||||
|
@ -78,7 +80,7 @@ public class StoreRecentTransactions : ViewComponent
|
|||
Balance = tx.BalanceChange.ShowMoney(network),
|
||||
Currency = vm.CryptoCode,
|
||||
IsConfirmed = tx.Confirmations != 0,
|
||||
Link = _transactionLinkProviders.GetTransactionLink(pmi, tx.TransactionId.ToString()),
|
||||
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, tx.TransactionId.ToString()),
|
||||
Timestamp = tx.SeenAt,
|
||||
Labels = labels
|
||||
};
|
||||
|
|
|
@ -2,6 +2,8 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -12,17 +14,14 @@ namespace BTCPayServer.Components.StoreSelector
|
|||
public class StoreSelector : ViewComponent
|
||||
{
|
||||
private readonly StoreRepository _storeRepo;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public StoreSelector(
|
||||
StoreRepository storeRepo,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_userManager = userManager;
|
||||
_networkProvider = networkProvider;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync()
|
||||
|
@ -34,21 +33,12 @@ namespace BTCPayServer.Components.StoreSelector
|
|||
var options = stores
|
||||
.Where(store => !store.Archived)
|
||||
.Select(store =>
|
||||
new StoreSelectorOption
|
||||
{
|
||||
var cryptoCode = store
|
||||
.GetSupportedPaymentMethods(_networkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault()?
|
||||
.Network.CryptoCode;
|
||||
var walletId = cryptoCode != null ? new WalletId(store.Id, cryptoCode) : null;
|
||||
return new StoreSelectorOption
|
||||
{
|
||||
Text = store.StoreName,
|
||||
Value = store.Id,
|
||||
Selected = store.Id == currentStore?.Id,
|
||||
WalletId = walletId,
|
||||
Store = store
|
||||
};
|
||||
Text = store.StoreName,
|
||||
Value = store.Id,
|
||||
Selected = store.Id == currentStore?.Id,
|
||||
Store = store
|
||||
})
|
||||
.OrderBy(s => s.Text)
|
||||
.ToList();
|
||||
|
|
|
@ -17,7 +17,6 @@ namespace BTCPayServer.Components.StoreSelector
|
|||
public bool Selected { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Value { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public StoreData Store { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
{
|
||||
<div class="ct-chart"></div>
|
||||
}
|
||||
else if (!Model.Store.GetSupportedPaymentMethods(NetworkProvider).Any(method => method.PaymentId.PaymentType == BitcoinPaymentType.Instance && method.PaymentId.CryptoCode == Model.CryptoCode))
|
||||
else if (Model.Store.GetPaymentMethodConfig(PaymentTypes.CHAIN.GetPaymentMethodId(Model.CryptoCode)) is null)
|
||||
{
|
||||
<p>
|
||||
We would like to show you a chart of your balance but you have not yet <a href="@Url.Action("SetupWallet", "UIStores", new {storeId = Model.Store.Id, cryptoCode = Model.CryptoCode})">configured a wallet</a>.
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
|
@ -29,19 +30,22 @@ public class StoreWalletBalance : ViewComponent
|
|||
private readonly WalletHistogramService _walletHistogramService;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public StoreWalletBalance(
|
||||
StoreRepository storeRepo,
|
||||
CurrencyNameTable currencies,
|
||||
WalletHistogramService walletHistogramService,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
_storeRepo = storeRepo;
|
||||
_currencies = currencies;
|
||||
_walletProvider = walletProvider;
|
||||
_walletHistogramService = walletHistogramService;
|
||||
_networkProvider = networkProvider;
|
||||
_walletHistogramService = walletHistogramService;
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
||||
|
@ -71,11 +75,12 @@ public class StoreWalletBalance : ViewComponent
|
|||
{
|
||||
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(3));
|
||||
var wallet = _walletProvider.GetWallet(_networkProvider.DefaultNetwork);
|
||||
var derivation = store.GetDerivationSchemeSettings(_networkProvider, walletId.CryptoCode);
|
||||
var derivation = store.GetDerivationSchemeSettings(_handlers, walletId.CryptoCode);
|
||||
var network = _handlers.GetBitcoinHandler(walletId.CryptoCode).Network;
|
||||
if (derivation is not null)
|
||||
{
|
||||
var balance = await wallet.GetBalance(derivation.AccountDerivation, cts.Token);
|
||||
vm.Balance = balance.Available.GetValue(derivation.Network);
|
||||
vm.Balance = balance.Available.GetValue(network);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace BTCPayServer.Components.WalletNav
|
|||
public class WalletNav : ViewComponent
|
||||
{
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly UIWalletsController _walletsController;
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
|
@ -33,12 +34,14 @@ namespace BTCPayServer.Components.WalletNav
|
|||
|
||||
public WalletNav(
|
||||
BTCPayWalletProvider walletProvider,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
UIWalletsController walletsController,
|
||||
CurrencyNameTable currencies,
|
||||
RateFetcher rateFetcher)
|
||||
{
|
||||
_walletProvider = walletProvider;
|
||||
_handlers = handlers;
|
||||
_networkProvider = networkProvider;
|
||||
_walletsController = walletsController;
|
||||
_currencies = currencies;
|
||||
|
@ -51,7 +54,7 @@ namespace BTCPayServer.Components.WalletNav
|
|||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var defaultCurrency = store.GetStoreBlob().DefaultCurrency;
|
||||
var derivation = store.GetDerivationSchemeSettings(_networkProvider, walletId.CryptoCode);
|
||||
var derivation = store.GetDerivationSchemeSettings(_handlers, walletId.CryptoCode);
|
||||
var balance = await wallet.GetBalance(derivation?.AccountDerivation) switch
|
||||
{
|
||||
{ Available: null, Total: var total } => total,
|
||||
|
|
|
@ -26,12 +26,15 @@ namespace BTCPayServer.Controllers
|
|||
public class BitpayInvoiceController : Controller
|
||||
{
|
||||
private readonly UIInvoiceController _InvoiceController;
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension> _bitpayExtensions;
|
||||
private readonly InvoiceRepository _InvoiceRepository;
|
||||
|
||||
public BitpayInvoiceController(UIInvoiceController invoiceController,
|
||||
Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension> bitpayExtensions,
|
||||
InvoiceRepository invoiceRepository)
|
||||
{
|
||||
_InvoiceController = invoiceController;
|
||||
_bitpayExtensions = bitpayExtensions;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
}
|
||||
|
||||
|
@ -56,7 +59,7 @@ namespace BTCPayServer.Controllers
|
|||
})).FirstOrDefault();
|
||||
if (invoice == null)
|
||||
throw new BitpayHttpException(404, "Object not found");
|
||||
return new DataWrapper<InvoiceResponse>(invoice.EntityToDTO());
|
||||
return new DataWrapper<InvoiceResponse>(invoice.EntityToDTO(_bitpayExtensions, Url));
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
|
@ -90,7 +93,7 @@ namespace BTCPayServer.Controllers
|
|||
};
|
||||
|
||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||
.Select((o) => o.EntityToDTO()).ToArray();
|
||||
.Select((o) => o.EntityToDTO(_bitpayExtensions, Url)).ToArray();
|
||||
|
||||
return Json(DataWrapper.Create(entities));
|
||||
}
|
||||
|
@ -100,7 +103,7 @@ namespace BTCPayServer.Controllers
|
|||
CancellationToken cancellationToken = default, Action<InvoiceEntity> entityManipulator = null)
|
||||
{
|
||||
var entity = await CreateInvoiceCoreRaw(invoice, store, serverUrl, additionalTags, cancellationToken, entityManipulator);
|
||||
var resp = entity.EntityToDTO();
|
||||
var resp = entity.EntityToDTO(_bitpayExtensions, Url);
|
||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||
}
|
||||
|
||||
|
@ -178,7 +181,10 @@ namespace BTCPayServer.Controllers
|
|||
excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p));
|
||||
}
|
||||
entity.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
entity.DefaultPaymentMethod = invoice.DefaultPaymentMethod;
|
||||
if (invoice.DefaultPaymentMethod is not null && PaymentMethodId.TryParse(invoice.DefaultPaymentMethod, out var defaultPaymentMethod))
|
||||
{
|
||||
entity.DefaultPaymentMethod = defaultPaymentMethod;
|
||||
}
|
||||
entity.RequiresRefundEmail = invoice.RequiresRefundEmail;
|
||||
|
||||
return await _InvoiceController.CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken, entityManipulator);
|
||||
|
|
|
@ -9,9 +9,12 @@ using BTCPayServer.Abstractions.Constants;
|
|||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -29,7 +32,9 @@ namespace BTCPayServer.Controllers
|
|||
readonly RateFetcher _rateProviderFactory;
|
||||
readonly BTCPayNetworkProvider _networkProvider;
|
||||
readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
readonly StoreRepository _storeRepo;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
|
||||
private StoreData CurrentStore => HttpContext.GetStoreData();
|
||||
|
||||
|
@ -37,12 +42,16 @@ namespace BTCPayServer.Controllers
|
|||
RateFetcher rateProviderFactory,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
StoreRepository storeRepo,
|
||||
CurrencyNameTable currencyNameTable)
|
||||
InvoiceRepository invoiceRepository,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
_rateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
|
||||
_networkProvider = networkProvider;
|
||||
_storeRepo = storeRepo;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_currencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
[Route("rates/{baseCurrency}")]
|
||||
|
@ -50,11 +59,17 @@ namespace BTCPayServer.Controllers
|
|||
[BitpayAPIConstraint]
|
||||
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, string cryptoCode = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var supportedMethods = CurrentStore.GetSupportedPaymentMethods(_networkProvider);
|
||||
|
||||
var currencyCodes = supportedMethods.Where(method => !string.IsNullOrEmpty(method.PaymentId.CryptoCode))
|
||||
.Select(method => method.PaymentId.CryptoCode).Distinct();
|
||||
|
||||
var inv = _invoiceRepository.CreateNewInvoice(CurrentStore.Id);
|
||||
inv.Currency = baseCurrency;
|
||||
var ctx = new InvoiceCreationContext(CurrentStore, CurrentStore.GetStoreBlob(), inv, new Logging.InvoiceLogs(), _handlers, null);
|
||||
ctx.SetLazyActivation(true);
|
||||
await ctx.BeforeFetchingRates();
|
||||
var currencyCodes = ctx
|
||||
.PaymentMethodContexts
|
||||
.SelectMany(c => c.Value.RequiredRates)
|
||||
.Where(c => c.Left.Equals(baseCurrency, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(c => c.Right)
|
||||
.ToHashSet();
|
||||
var currencypairs = BuildCurrencyPairs(currencyCodes, baseCurrency);
|
||||
|
||||
var result = await GetRates2(currencypairs, null, cryptoCode, cancellationToken);
|
||||
|
|
|
@ -11,6 +11,7 @@ using BTCPayServer.Client.Models;
|
|||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Greenfield;
|
||||
using BTCPayServer.Services;
|
||||
|
@ -21,7 +22,9 @@ using Microsoft.AspNetCore.Cors;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
|
||||
|
@ -42,6 +45,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
private readonly InvoiceActivator _invoiceActivator;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentLinkExtension> _paymentLinkExtensions;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public LanguageService LanguageService { get; }
|
||||
|
||||
|
@ -51,7 +56,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
InvoiceActivator invoiceActivator,
|
||||
PullPaymentHostedService pullPaymentService,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
IAuthorizationService authorizationService)
|
||||
IAuthorizationService authorizationService,
|
||||
Dictionary<PaymentMethodId, IPaymentLinkExtension> paymentLinkExtensions,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
_invoiceController = invoiceController;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
|
@ -63,6 +70,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
_pullPaymentService = pullPaymentService;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_authorizationService = authorizationService;
|
||||
_paymentLinkExtensions = paymentLinkExtensions;
|
||||
_handlers = handlers;
|
||||
LanguageService = languageService;
|
||||
}
|
||||
|
||||
|
@ -347,7 +356,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
|
||||
{
|
||||
await _invoiceActivator.ActivateInvoicePaymentMethod(paymentMethodId, invoice, store);
|
||||
await _invoiceActivator.ActivateInvoicePaymentMethod(invoiceId, paymentMethodId);
|
||||
return Ok();
|
||||
}
|
||||
ModelState.AddModelError(nameof(paymentMethod), "Invalid payment method");
|
||||
|
@ -384,33 +393,31 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
{
|
||||
return this.CreateAPIError("non-refundable", "Cannot refund this invoice");
|
||||
}
|
||||
PaymentMethod? invoicePaymentMethod = null;
|
||||
PaymentPrompt? paymentPrompt = null;
|
||||
PaymentMethodId? paymentMethodId = null;
|
||||
if (request.PaymentMethod is not null && PaymentMethodId.TryParse(request.PaymentMethod, out paymentMethodId))
|
||||
{
|
||||
invoicePaymentMethod = invoice.GetPaymentMethods().SingleOrDefault(method => method.GetId() == paymentMethodId);
|
||||
paymentPrompt = invoice.GetPaymentPrompt(paymentMethodId);
|
||||
}
|
||||
if (invoicePaymentMethod is null)
|
||||
if (paymentPrompt is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PaymentMethod), "Please select one of the payment methods which were available for the original invoice");
|
||||
}
|
||||
if (request.RefundVariant is null)
|
||||
ModelState.AddModelError(nameof(request.RefundVariant), "`refundVariant` is mandatory");
|
||||
if (!ModelState.IsValid || invoicePaymentMethod is null || paymentMethodId is null)
|
||||
if (!ModelState.IsValid || paymentPrompt is null || paymentMethodId is null)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
var accounting = invoicePaymentMethod.Calculate();
|
||||
var accounting = paymentPrompt.Calculate();
|
||||
var cryptoPaid = accounting.Paid;
|
||||
var cdCurrency = _currencyNameTable.GetCurrencyData(invoice.Currency, true);
|
||||
var paidCurrency = Math.Round(cryptoPaid * invoicePaymentMethod.Rate, cdCurrency.Divisibility);
|
||||
var paidCurrency = Math.Round(cryptoPaid * paymentPrompt.Rate, cdCurrency.Divisibility);
|
||||
var rateResult = await _rateProvider.FetchRate(
|
||||
new CurrencyPair(paymentMethodId.CryptoCode, invoice.Currency),
|
||||
new CurrencyPair(paymentPrompt.Currency, invoice.Currency),
|
||||
store.GetStoreBlob().GetRateRules(_networkProvider),
|
||||
cancellationToken
|
||||
);
|
||||
var cryptoCode = invoicePaymentMethod.GetId().CryptoCode;
|
||||
var paymentMethodDivisibility = _currencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
|
||||
var paidAmount = cryptoPaid.RoundToSignificant(paymentMethodDivisibility);
|
||||
var paidAmount = cryptoPaid.RoundToSignificant(paymentPrompt.Divisibility);
|
||||
var createPullPayment = new CreatePullPayment
|
||||
{
|
||||
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration,
|
||||
|
@ -436,17 +443,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var appliedDivisibility = paymentMethodDivisibility;
|
||||
var appliedDivisibility = paymentPrompt.Divisibility;
|
||||
switch (request.RefundVariant)
|
||||
{
|
||||
case RefundVariant.RateThen:
|
||||
createPullPayment.Currency = cryptoCode;
|
||||
createPullPayment.Currency = paymentPrompt.Currency;
|
||||
createPullPayment.Amount = paidAmount;
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
|
||||
case RefundVariant.CurrentRate:
|
||||
createPullPayment.Currency = cryptoCode;
|
||||
createPullPayment.Currency = paymentPrompt.Currency;
|
||||
createPullPayment.Amount = Math.Round(paidCurrency / rateResult.BidAsk.Bid, appliedDivisibility);
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
|
@ -469,7 +476,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
}
|
||||
|
||||
var dueAmount = accounting.TotalDue;
|
||||
createPullPayment.Currency = cryptoCode;
|
||||
createPullPayment.Currency = paymentPrompt.Currency;
|
||||
createPullPayment.Amount = Math.Round(paidAmount - dueAmount, appliedDivisibility);
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
|
@ -501,7 +508,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
createPullPayment.Currency = request.CustomCurrency;
|
||||
createPullPayment.Amount = request.CustomAmount.Value;
|
||||
createPullPayment.AutoApproveClaims = paymentMethodId.CryptoCode == request.CustomCurrency;
|
||||
createPullPayment.AutoApproveClaims = paymentPrompt.Currency == request.CustomCurrency;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -569,49 +576,52 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity, bool includeAccountedPaymentOnly)
|
||||
{
|
||||
return entity.GetPaymentMethods().Select(
|
||||
method =>
|
||||
return entity.GetPaymentPrompts().Select(
|
||||
prompt =>
|
||||
{
|
||||
var accounting = method.Calculate();
|
||||
var details = method.GetPaymentMethodDetails();
|
||||
var payments = method.ParentEntity.GetPayments(includeAccountedPaymentOnly).Where(paymentEntity =>
|
||||
paymentEntity.GetPaymentMethodId() == method.GetId());
|
||||
_handlers.TryGetValue(prompt.PaymentMethodId, out var handler);
|
||||
var accounting = prompt.Currency is not null ? prompt.Calculate() : null;
|
||||
var payments = prompt.ParentEntity.GetPayments(includeAccountedPaymentOnly).Where(paymentEntity =>
|
||||
paymentEntity.PaymentMethodId == prompt.PaymentMethodId);
|
||||
_paymentLinkExtensions.TryGetValue(prompt.PaymentMethodId, out var paymentLinkExtension);
|
||||
|
||||
var details = prompt.Details;
|
||||
if (handler is not null && prompt.Activated)
|
||||
details = JToken.FromObject(handler.ParsePaymentPromptDetails(details), handler.Serializer.ForAPI());
|
||||
return new InvoicePaymentMethodDataModel
|
||||
{
|
||||
Activated = details.Activated,
|
||||
PaymentMethod = method.GetId().ToStringNormalized(),
|
||||
CryptoCode = method.GetId().CryptoCode,
|
||||
Destination = details.GetPaymentDestination(),
|
||||
Rate = method.Rate,
|
||||
Due = accounting.DueUncapped,
|
||||
TotalPaid = accounting.Paid,
|
||||
PaymentMethodPaid = accounting.CryptoPaid,
|
||||
Amount = accounting.TotalDue,
|
||||
NetworkFee = accounting.NetworkFee,
|
||||
PaymentLink =
|
||||
method.GetId().PaymentType.GetPaymentLink(method.Network, entity, details, accounting.Due,
|
||||
Request.GetAbsoluteRoot()),
|
||||
Activated = prompt.Activated,
|
||||
PaymentMethodId = prompt.PaymentMethodId.ToString(),
|
||||
Currency = prompt.Currency,
|
||||
Destination = prompt.Destination,
|
||||
Rate = prompt.Currency is not null ? prompt.Rate : 0m,
|
||||
Due = accounting?.DueUncapped ?? 0m,
|
||||
TotalPaid = accounting?.Paid ?? 0m,
|
||||
PaymentMethodPaid = accounting?.PaymentMethodPaid ?? 0m,
|
||||
Amount = accounting?.TotalDue ?? 0m,
|
||||
PaymentMethodFee = accounting?.PaymentMethodFee ?? 0m,
|
||||
PaymentLink = (prompt.Activated ? paymentLinkExtension?.GetPaymentLink(prompt, Url) : null) ?? string.Empty,
|
||||
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
|
||||
AdditionalData = details.GetAdditionalData()
|
||||
AdditionalData = prompt.Details
|
||||
};
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public static InvoicePaymentMethodDataModel.Payment ToPaymentModel(InvoiceEntity entity, PaymentEntity paymentEntity)
|
||||
{
|
||||
var data = paymentEntity.GetCryptoPaymentData();
|
||||
return new InvoicePaymentMethodDataModel.Payment()
|
||||
{
|
||||
Destination = data.GetDestination(),
|
||||
Id = data.GetPaymentId(),
|
||||
Status = !paymentEntity.Accounted
|
||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid
|
||||
: data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) || data.PaymentCompleted(paymentEntity)
|
||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled
|
||||
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
||||
Fee = paymentEntity.NetworkFee,
|
||||
Value = data.GetValue(),
|
||||
Destination = paymentEntity.Destination,
|
||||
Id = paymentEntity.Id,
|
||||
Status = paymentEntity.Status switch
|
||||
{
|
||||
PaymentStatus.Processing => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
||||
PaymentStatus.Settled => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled,
|
||||
PaymentStatus.Unaccounted => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid,
|
||||
_ => throw new NotSupportedException(paymentEntity.Status.ToString())
|
||||
},
|
||||
Fee = paymentEntity.PaymentMethodFee,
|
||||
Value = paymentEntity.Value,
|
||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
||||
};
|
||||
}
|
||||
|
@ -656,8 +666,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
Monitoring = entity.MonitoringExpiration - entity.ExpirationTime,
|
||||
PaymentTolerance = entity.PaymentTolerance,
|
||||
PaymentMethods =
|
||||
entity.GetPaymentMethods().Select(method => method.GetId().ToStringNormalized()).ToArray(),
|
||||
DefaultPaymentMethod = entity.DefaultPaymentMethod,
|
||||
entity.GetPaymentPrompts().Select(method => method.PaymentMethodId.ToString()).ToArray(),
|
||||
DefaultPaymentMethod = entity.DefaultPaymentMethod?.ToString(),
|
||||
SpeedPolicy = entity.SpeedPolicy,
|
||||
DefaultLanguage = entity.DefaultLanguage,
|
||||
RedirectAutomatically = entity.RedirectAutomatically,
|
||||
|
|
|
@ -9,6 +9,7 @@ using BTCPayServer.HostedServices;
|
|||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -22,19 +23,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
[EnableCors(CorsPolicies.All)]
|
||||
public class GreenfieldInternalLightningNodeApiController : GreenfieldLightningNodeApiController
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly LightningClientFactoryService _lightningClientFactory;
|
||||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public GreenfieldInternalLightningNodeApiController(
|
||||
BTCPayNetworkProvider btcPayNetworkProvider, PoliciesSettings policiesSettings, LightningClientFactoryService lightningClientFactory,
|
||||
PoliciesSettings policiesSettings, LightningClientFactoryService lightningClientFactory,
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
IAuthorizationService authorizationService) : base(
|
||||
btcPayNetworkProvider, policiesSettings, authorizationService)
|
||||
IAuthorizationService authorizationService,
|
||||
PaymentMethodHandlerDictionary handlers
|
||||
) : base(policiesSettings, authorizationService, handlers)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
|
@ -135,7 +137,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var network = GetNetwork(cryptoCode);
|
||||
if (network is null)
|
||||
throw ErrorCryptoCodeNotFound();
|
||||
if (!_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode,
|
||||
|
|
|
@ -12,6 +12,8 @@ using BTCPayServer.Lightning;
|
|||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -27,18 +29,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
{
|
||||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||
private readonly LightningClientFactoryService _lightningClientFactory;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public GreenfieldStoreLightningNodeApiController(
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
LightningClientFactoryService lightningClientFactory, PaymentMethodHandlerDictionary handlers,
|
||||
PoliciesSettings policiesSettings,
|
||||
IAuthorizationService authorizationService) : base(
|
||||
btcPayNetworkProvider, policiesSettings, authorizationService)
|
||||
IAuthorizationService authorizationService) : base(policiesSettings, authorizationService, handlers)
|
||||
{
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
|
@ -138,22 +139,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
protected override Task<ILightningClient> GetLightningClient(string cryptoCode,
|
||||
bool doingAdminThings)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network == null)
|
||||
if (!_handlers.TryGetValue(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), out var o) ||
|
||||
o is not LightningLikePaymentHandler handler)
|
||||
{
|
||||
throw ErrorCryptoCodeNotFound();
|
||||
}
|
||||
|
||||
var network = handler.Network;
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
throw new JsonHttpException(StoreNotFound());
|
||||
}
|
||||
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
var id = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id, _handlers);
|
||||
if (existing == null)
|
||||
throw ErrorLightningNodeNotConfiguredForStore();
|
||||
if (existing.GetExternalLightningUrl() is {} connectionString)
|
||||
|
|
|
@ -6,8 +6,11 @@ using BTCPayServer.Abstractions.Extensions;
|
|||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
@ -26,16 +29,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
public abstract class GreenfieldLightningNodeApiController : Controller
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PoliciesSettings _policiesSettings;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
protected GreenfieldLightningNodeApiController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
protected GreenfieldLightningNodeApiController(
|
||||
PoliciesSettings policiesSettings,
|
||||
IAuthorizationService authorizationService)
|
||||
IAuthorizationService authorizationService,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_policiesSettings = policiesSettings;
|
||||
_authorizationService = authorizationService;
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetInfo(string cryptoCode, CancellationToken cancellationToken = default)
|
||||
|
@ -207,7 +212,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
public virtual async Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var network = GetNetwork(cryptoCode);
|
||||
BOLT11PaymentRequest bolt11 = null;
|
||||
|
||||
if (string.IsNullOrEmpty(lightningInvoice.BOLT11) ||
|
||||
|
@ -336,7 +341,11 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return this.CreateAPIError("generic-error", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected BTCPayNetwork GetNetwork(string cryptoCode)
|
||||
=> _handlers.TryGetValue(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), out var h)
|
||||
&& h is IHasNetwork { Network: var network }
|
||||
? network
|
||||
: null;
|
||||
protected JsonHttpException ErrorLightningNodeNotConfiguredForStore()
|
||||
{
|
||||
return new JsonHttpException(this.CreateAPIError(404, "lightning-not-configured", "The lightning node is not set up"));
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
{
|
||||
Name = factory.Processor,
|
||||
FriendlyName = factory.FriendlyName,
|
||||
PaymentMethods = factory.GetSupportedPaymentMethods().Select(id => id.ToStringNormalized())
|
||||
PaymentMethods = factory.GetSupportedPaymentMethods().Select(id => id.ToString())
|
||||
.ToArray()
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
public ActionResult ServerInfo()
|
||||
{
|
||||
var supportedPaymentMethods = _paymentMethodHandlerDictionary
|
||||
.SelectMany(handler => handler.GetSupportedPaymentMethods().Select(id => id.ToString()))
|
||||
.Select(handler => handler.PaymentMethodId.ToString())
|
||||
.Distinct();
|
||||
|
||||
ServerInfoData model = new ServerInfoData2
|
||||
|
|
|
@ -38,14 +38,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
public async Task<IActionResult> GetStoreLightningAutomatedPayoutProcessors(
|
||||
string storeId, string? paymentMethod)
|
||||
{
|
||||
paymentMethod = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod).ToString() : null;
|
||||
var paymentMethodId = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod) : null;
|
||||
var configured =
|
||||
await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { LightningAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = paymentMethod is null ? null : new[] { paymentMethod }
|
||||
PaymentMethods = paymentMethodId is null ? null : new[] { paymentMethodId }
|
||||
});
|
||||
|
||||
return Ok(configured.Select(ToModel).ToArray());
|
||||
|
@ -80,20 +80,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
|
||||
var paymentMethodId = PaymentMethodId.Parse(paymentMethod);
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { LightningAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = new[] { paymentMethod }
|
||||
PaymentMethods = new[] { paymentMethodId }
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.HasTypedBlob<LightningAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = paymentMethod;
|
||||
activeProcessor.PaymentMethod = paymentMethodId.ToString();
|
||||
activeProcessor.Processor = LightningAutomatedPayoutSenderFactory.ProcessorName;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
|
|
|
@ -39,14 +39,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
public async Task<IActionResult> GetStoreOnChainAutomatedPayoutProcessors(
|
||||
string storeId, string? paymentMethod)
|
||||
{
|
||||
paymentMethod = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod).ToString() : null;
|
||||
var paymentMethodId = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod) : null;
|
||||
var configured =
|
||||
await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = paymentMethod is null ? null : new[] { paymentMethod }
|
||||
PaymentMethods = paymentMethodId is null ? null : new[] { paymentMethodId }
|
||||
});
|
||||
|
||||
return Ok(configured.Select(ToModel).ToArray());
|
||||
|
@ -86,20 +86,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
ModelState.AddModelError(nameof(request.FeeBlockTarget), "The feeBlockTarget should be between 1 and 1000");
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
|
||||
var paymentMethodId = PaymentMethodId.Parse(paymentMethod);
|
||||
var activeProcessor =
|
||||
(await _payoutProcessorService.GetProcessors(
|
||||
new PayoutProcessorService.PayoutProcessorQuery()
|
||||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
||||
PaymentMethods = new[] { paymentMethod }
|
||||
PaymentMethods = new[] { paymentMethodId }
|
||||
}))
|
||||
.FirstOrDefault();
|
||||
activeProcessor ??= new PayoutProcessorData();
|
||||
activeProcessor.HasTypedBlob<OnChainAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
||||
activeProcessor.StoreId = storeId;
|
||||
activeProcessor.PaymentMethod = paymentMethod;
|
||||
activeProcessor.PaymentMethod = paymentMethodId.ToString();
|
||||
activeProcessor.Processor = OnChainAutomatedPayoutSenderFactory.ProcessorName;
|
||||
var tcs = new TaskCompletionSource();
|
||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||
|
|
|
@ -1,170 +0,0 @@
|
|||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public class GreenfieldStoreLNURLPayPaymentMethodsController : ControllerBase
|
||||
{
|
||||
private StoreData Store => HttpContext.GetStoreData();
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
|
||||
public GreenfieldStoreLNURLPayPaymentMethodsController(
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
}
|
||||
|
||||
public static IEnumerable<LNURLPayPaymentMethodData> GetLNURLPayPaymentMethods(StoreData store,
|
||||
BTCPayNetworkProvider networkProvider, bool? enabled)
|
||||
{
|
||||
var blob = store.GetStoreBlob();
|
||||
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
|
||||
|
||||
return store.GetSupportedPaymentMethods(networkProvider)
|
||||
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.LNURLPay)
|
||||
.OfType<LNURLPaySupportedPaymentMethod>()
|
||||
.Select(paymentMethod =>
|
||||
new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.UseBech32Scheme,
|
||||
paymentMethod.LUD12Enabled
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LNURLPay")]
|
||||
public ActionResult<IEnumerable<LNURLPayPaymentMethodData>> GetLNURLPayPaymentMethods(
|
||||
string storeId,
|
||||
[FromQuery] bool? enabled)
|
||||
{
|
||||
return Ok(GetLNURLPayPaymentMethods(Store, _btcPayNetworkProvider, enabled));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public IActionResult GetLNURLPayPaymentMethod(string storeId, string cryptoCode)
|
||||
{
|
||||
AssertCryptoCodeWallet(cryptoCode, out _);
|
||||
var method = GetExistingLNURLPayPaymentMethod(cryptoCode);
|
||||
if (method is null)
|
||||
{
|
||||
return this.CreateAPIError(404, "paymentmethod-not-found", "The LNURL Payment Method isn't activated");
|
||||
}
|
||||
|
||||
return Ok(method);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public async Task<IActionResult> RemoveLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode)
|
||||
{
|
||||
|
||||
AssertCryptoCodeWallet(cryptoCode, out _);
|
||||
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var store = Store;
|
||||
store.SetSupportedPaymentMethod(id, null);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public async Task<IActionResult> UpdateLNURLPayPaymentMethod(string storeId, string cryptoCode,
|
||||
[FromBody] LNURLPayPaymentMethodData paymentMethodData)
|
||||
{
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
|
||||
AssertCryptoCodeWallet(cryptoCode, out _);
|
||||
|
||||
var lnMethod = GreenfieldStoreLightningNetworkPaymentMethodsController.GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider,
|
||||
cryptoCode, Store);
|
||||
|
||||
if ((lnMethod is null || lnMethod.Enabled is false) && paymentMethodData.Enabled)
|
||||
{
|
||||
ModelState.AddModelError(nameof(LNURLPayPaymentMethodData.Enabled),
|
||||
"LNURL Pay cannot be enabled unless the lightning payment method is configured and enabled on this store");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
var paymentMethod = new LNURLPaySupportedPaymentMethod
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme,
|
||||
LUD12Enabled = paymentMethodData.LUD12Enabled
|
||||
};
|
||||
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
storeBlob.SetExcluded(paymentMethodId, !paymentMethodData.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(GetExistingLNURLPayPaymentMethod(cryptoCode, store));
|
||||
}
|
||||
|
||||
private LNURLPayPaymentMethodData? GetExistingLNURLPayPaymentMethod(string cryptoCode,
|
||||
StoreData? store = null)
|
||||
{
|
||||
store ??= Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var paymentMethod = store
|
||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LNURLPaySupportedPaymentMethod>()
|
||||
.FirstOrDefault(method => method.PaymentId == id);
|
||||
|
||||
var excluded = storeBlob.IsExcluded(id);
|
||||
return paymentMethod is null
|
||||
? null
|
||||
: new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excluded,
|
||||
paymentMethod.UseBech32Scheme,
|
||||
paymentMethod.LUD12Enabled
|
||||
);
|
||||
}
|
||||
private void AssertCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network)
|
||||
{
|
||||
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network is null)
|
||||
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode", "This crypto code isn't set up in this BTCPay Server instance"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,244 +0,0 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public class GreenfieldStoreLightningNetworkPaymentMethodsController : ControllerBase
|
||||
{
|
||||
private StoreData Store => HttpContext.GetStoreData();
|
||||
|
||||
public PoliciesSettings PoliciesSettings { get; }
|
||||
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly LightningClientFactoryService _lightningClientFactoryService;
|
||||
|
||||
public GreenfieldStoreLightningNetworkPaymentMethodsController(
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IAuthorizationService authorizationService,
|
||||
LightningClientFactoryService lightningClientFactoryService,
|
||||
PoliciesSettings policiesSettings)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_authorizationService = authorizationService;
|
||||
_lightningClientFactoryService = lightningClientFactoryService;
|
||||
PoliciesSettings = policiesSettings;
|
||||
}
|
||||
|
||||
public static IEnumerable<LightningNetworkPaymentMethodData> GetLightningPaymentMethods(StoreData store,
|
||||
BTCPayNetworkProvider networkProvider, bool? enabled)
|
||||
{
|
||||
var blob = store.GetStoreBlob();
|
||||
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
|
||||
|
||||
return store.GetSupportedPaymentMethods(networkProvider)
|
||||
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.LightningLike)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.Select(paymentMethod =>
|
||||
new LightningNetworkPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetExternalLightningUrl()?.ToString() ??
|
||||
paymentMethod.GetDisplayableConnectionString(),
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.PaymentId.ToStringNormalized()
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork")]
|
||||
public ActionResult<IEnumerable<LightningNetworkPaymentMethodData>> GetLightningPaymentMethods(
|
||||
string storeId,
|
||||
[FromQuery] bool? enabled)
|
||||
{
|
||||
return Ok(GetLightningPaymentMethods(Store, _btcPayNetworkProvider, enabled));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
|
||||
public ActionResult<LightningNetworkPaymentMethodData> GetLightningNetworkPaymentMethod(string storeId, string cryptoCode)
|
||||
{
|
||||
AssertSupportLightning(cryptoCode);
|
||||
|
||||
var method = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, Store);
|
||||
if (method is null)
|
||||
{
|
||||
throw ErrorPaymentMethodNotConfigured();
|
||||
}
|
||||
|
||||
return Ok(method);
|
||||
}
|
||||
|
||||
protected JsonHttpException ErrorPaymentMethodNotConfigured()
|
||||
{
|
||||
return new JsonHttpException(this.CreateAPIError(404, "paymentmethod-not-configured", "The lightning payment method is not set up"));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
|
||||
public async Task<IActionResult> RemoveLightningNetworkPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode)
|
||||
{
|
||||
AssertSupportLightning(cryptoCode);
|
||||
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var store = Store;
|
||||
store.SetSupportedPaymentMethod(id, null);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
|
||||
public async Task<IActionResult> UpdateLightningNetworkPaymentMethod(string storeId, string cryptoCode,
|
||||
[FromBody] UpdateLightningNetworkPaymentMethodRequest request)
|
||||
{
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
AssertSupportLightning(cryptoCode);
|
||||
|
||||
if (string.IsNullOrEmpty(request.ConnectionString))
|
||||
{
|
||||
ModelState.AddModelError(nameof(LightningNetworkPaymentMethodData.ConnectionString),
|
||||
"Missing connectionString");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
LightningSupportedPaymentMethod? paymentMethod = null;
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var existing = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, store);
|
||||
if (existing == null || existing.ConnectionString != request.ConnectionString)
|
||||
{
|
||||
if (request.ConnectionString == LightningSupportedPaymentMethod.InternalNode)
|
||||
{
|
||||
if (!await CanUseInternalLightning())
|
||||
{
|
||||
return this.CreateAPIPermissionError(Policies.CanUseInternalLightningNode, $"You are not authorized to use the internal lightning node. Either add '{Policies.CanUseInternalLightningNode}' to an API Key, or allow non-admin users to use the internal lightning node in the server settings.");
|
||||
}
|
||||
|
||||
paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetInternalNode();
|
||||
}
|
||||
else
|
||||
{
|
||||
ILightningClient? lightningClient;
|
||||
try
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
lightningClient = _lightningClientFactoryService.Create(request.ConnectionString, network);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ConnectionString), $"Invalid URL ({e.Message})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
// if (connectionString.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
// {
|
||||
// ModelState.AddModelError(nameof(request.ConnectionString),
|
||||
// $"BTCPay does not support gRPC connections");
|
||||
// return this.CreateValidationError(ModelState);
|
||||
// }
|
||||
|
||||
if (!await CanManageServer() && !lightningClient.IsSafe())
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ConnectionString),
|
||||
$"You do not have 'btcpay.server.canmodifyserversettings' rights, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'.");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetLightningUrl(lightningClient);
|
||||
}
|
||||
}
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
storeBlob.SetExcluded(paymentMethodId, !request.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, store));
|
||||
}
|
||||
|
||||
public static LightningNetworkPaymentMethodData? GetExistingLightningLikePaymentMethod(BTCPayNetworkProvider btcPayNetworkProvider, string cryptoCode,
|
||||
StoreData store)
|
||||
{
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var paymentMethod = store
|
||||
.GetSupportedPaymentMethods(btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(method => method.PaymentId == id);
|
||||
|
||||
var excluded = storeBlob.IsExcluded(id);
|
||||
return paymentMethod is null
|
||||
? null
|
||||
: new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetDisplayableConnectionString(), !excluded,
|
||||
paymentMethod.PaymentId.ToStringNormalized());
|
||||
}
|
||||
|
||||
private BTCPayNetwork AssertSupportLightning(string cryptoCode)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network is null)
|
||||
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode", "This crypto code isn't set up in this BTCPay Server instance"));
|
||||
if (!(network.SupportLightning is true))
|
||||
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode", "This crypto code doesn't support lightning"));
|
||||
return network;
|
||||
}
|
||||
|
||||
private async Task<bool> CanUseInternalLightning()
|
||||
{
|
||||
return PoliciesSettings.AllowLightningInternalNodeForAll ||
|
||||
(await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded;
|
||||
}
|
||||
|
||||
private async Task<bool> CanManageServer()
|
||||
{
|
||||
return
|
||||
(await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,36 +6,39 @@ using BTCPayServer.Client;
|
|||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
public partial class GreenfieldStoreOnChainPaymentMethodsController
|
||||
{
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/generate")]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/generate")]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public async Task<IActionResult> GenerateOnChainWallet(string storeId, string cryptoCode,
|
||||
public async Task<IActionResult> GenerateOnChainWallet(string storeId,
|
||||
[ModelBinder(typeof(PaymentMethodIdModelBinder))]
|
||||
PaymentMethodId paymentMethodId,
|
||||
GenerateWalletRequest request)
|
||||
{
|
||||
|
||||
AssertCryptoCodeWallet(cryptoCode, out var network, out _);
|
||||
AssertCryptoCodeWallet(paymentMethodId, out var network, out _);
|
||||
|
||||
if (!_walletProvider.IsAvailable(network))
|
||||
{
|
||||
return this.CreateAPIError(503, "not-available",
|
||||
$"{cryptoCode} services are not currently available");
|
||||
$"{paymentMethodId} services are not currently available");
|
||||
}
|
||||
|
||||
var method = GetExistingBtcLikePaymentMethod(cryptoCode);
|
||||
if (method != null)
|
||||
if (IsConfigured(paymentMethodId, out _))
|
||||
{
|
||||
return this.CreateAPIError("already-configured",
|
||||
$"{cryptoCode} wallet is already configured for this store");
|
||||
$"{paymentMethodId} wallet is already configured for this store");
|
||||
}
|
||||
|
||||
var canUseHotWallet = await CanUseHotWallet();
|
||||
|
@ -64,13 +67,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
if (response == null)
|
||||
{
|
||||
return this.CreateAPIError(503, "not-available",
|
||||
$"{cryptoCode} services are not currently available");
|
||||
$"{paymentMethodId} services are not currently available");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.CreateAPIError(503, "not-available",
|
||||
$"{cryptoCode} error: {e.Message}");
|
||||
$"{paymentMethodId} error: {e.Message}");
|
||||
}
|
||||
|
||||
var derivationSchemeSettings = new DerivationSchemeSettings(response.DerivationScheme, network);
|
||||
|
@ -86,16 +89,22 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
store.SetSupportedPaymentMethod(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike),
|
||||
var handler = _handlers[paymentMethodId];
|
||||
store.SetPaymentMethodConfig(_handlers[paymentMethodId],
|
||||
derivationSchemeSettings);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
var rawResult = GetExistingBtcLikePaymentMethod(cryptoCode, store);
|
||||
var result = new OnChainPaymentMethodDataWithSensitiveData(rawResult.CryptoCode, rawResult.DerivationScheme,
|
||||
rawResult.Enabled, rawResult.Label, rawResult.AccountKeyPath, response.GetMnemonic(), derivationSchemeSettings.PaymentId.ToStringNormalized());
|
||||
|
||||
var result = new GenerateOnChainWalletResponse()
|
||||
{
|
||||
Enabled = !storeBlob.IsExcluded(paymentMethodId),
|
||||
PaymentMethodId = paymentMethodId.ToString(),
|
||||
Config = ((JObject)JToken.FromObject(derivationSchemeSettings, handler.Serializer.ForAPI())).ToObject<GenerateOnChainWalletResponse.ConfigData>(handler.Serializer.ForAPI())
|
||||
};
|
||||
result.Mnemonic = response.GetMnemonic();
|
||||
_eventAggregator.Publish(new WalletChangedEvent()
|
||||
{
|
||||
WalletId = new WalletId(storeId, cryptoCode)
|
||||
WalletId = new WalletId(storeId, network.CryptoCode)
|
||||
});
|
||||
return Ok(result);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
|
@ -9,8 +10,11 @@ using BTCPayServer.Client.Models;
|
|||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -19,6 +23,9 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Org.BouncyCastle.Asn1.X509.Qualified;
|
||||
using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
|
@ -33,71 +40,28 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
public PoliciesSettings PoliciesSettings { get; }
|
||||
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public GreenfieldStoreOnChainPaymentMethodsController(
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
IAuthorizationService authorizationService,
|
||||
ExplorerClientProvider explorerClientProvider,
|
||||
PoliciesSettings policiesSettings,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
EventAggregator eventAggregator)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_walletProvider = walletProvider;
|
||||
_authorizationService = authorizationService;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_eventAggregator = eventAggregator;
|
||||
PoliciesSettings = policiesSettings;
|
||||
}
|
||||
|
||||
public static IEnumerable<OnChainPaymentMethodData> GetOnChainPaymentMethods(StoreData store,
|
||||
BTCPayNetworkProvider networkProvider, bool? enabled)
|
||||
{
|
||||
var blob = store.GetStoreBlob();
|
||||
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
|
||||
|
||||
return store.GetSupportedPaymentMethods(networkProvider)
|
||||
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.BTCLike)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Select(strategy =>
|
||||
new OnChainPaymentMethodData(strategy.PaymentId.CryptoCode,
|
||||
strategy.AccountDerivation.ToString(), !excludedPaymentMethods.Match(strategy.PaymentId),
|
||||
strategy.Label, strategy.GetSigningAccountKeySettings().GetRootedKeyPath(),
|
||||
strategy.PaymentId.ToStringNormalized()))
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain")]
|
||||
public ActionResult<IEnumerable<OnChainPaymentMethodData>> GetOnChainPaymentMethods(
|
||||
string storeId,
|
||||
[FromQuery] bool? enabled)
|
||||
{
|
||||
return Ok(GetOnChainPaymentMethods(Store, _btcPayNetworkProvider, enabled));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}")]
|
||||
public ActionResult<OnChainPaymentMethodData> GetOnChainPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode)
|
||||
{
|
||||
AssertCryptoCodeWallet(cryptoCode, out BTCPayNetwork _, out BTCPayWallet _);
|
||||
var method = GetExistingBtcLikePaymentMethod(cryptoCode);
|
||||
if (method is null)
|
||||
{
|
||||
throw ErrorPaymentMethodNotConfigured();
|
||||
}
|
||||
|
||||
return Ok(method);
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
protected JsonHttpException ErrorPaymentMethodNotConfigured()
|
||||
|
@ -106,86 +70,64 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview")]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview")]
|
||||
public IActionResult GetOnChainPaymentMethodPreview(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
int offset = 0, int amount = 10)
|
||||
[ModelBinder(typeof(PaymentMethodIdModelBinder))]
|
||||
PaymentMethodId paymentMethodId,
|
||||
int offset = 0, int count = 10)
|
||||
{
|
||||
AssertCryptoCodeWallet(cryptoCode, out var network, out _);
|
||||
|
||||
var paymentMethod = GetExistingBtcLikePaymentMethod(cryptoCode);
|
||||
if (string.IsNullOrEmpty(paymentMethod?.DerivationScheme))
|
||||
AssertCryptoCodeWallet(paymentMethodId, out var network, out _);
|
||||
if (!IsConfigured(paymentMethodId, out var settings))
|
||||
{
|
||||
throw ErrorPaymentMethodNotConfigured();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
var strategy = network.GetDerivationSchemeParser().Parse(paymentMethod.DerivationScheme);
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
|
||||
var line = strategy.GetLineFor(deposit);
|
||||
var result = new OnChainPaymentMethodPreviewResultData();
|
||||
for (var i = offset; i < amount; i++)
|
||||
{
|
||||
var address = line.Derive((uint)i);
|
||||
result.Addresses.Add(
|
||||
new OnChainPaymentMethodPreviewResultData.OnChainPaymentMethodPreviewResultAddressItem()
|
||||
{
|
||||
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
|
||||
Address =
|
||||
network.NBXplorerNetwork.CreateAddress(strategy,deposit.GetKeyPath((uint)i), address.ScriptPubKey)
|
||||
.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
||||
"Invalid Derivation Scheme");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
return Ok(GetPreviewResultData(offset, count, network, settings.AccountDerivation));
|
||||
}
|
||||
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview")]
|
||||
public IActionResult GetProposedOnChainPaymentMethodPreview(
|
||||
[HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview")]
|
||||
public async Task<IActionResult> GetProposedOnChainPaymentMethodPreview(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
[FromBody] UpdateOnChainPaymentMethodRequest paymentMethodData,
|
||||
int offset = 0, int amount = 10)
|
||||
[ModelBinder(typeof(PaymentMethodIdModelBinder))]
|
||||
PaymentMethodId paymentMethodId,
|
||||
[FromBody] UpdatePaymentMethodRequest request = null,
|
||||
int offset = 0, int count = 10)
|
||||
{
|
||||
AssertCryptoCodeWallet(cryptoCode, out var network, out _);
|
||||
|
||||
if (string.IsNullOrEmpty(paymentMethodData?.DerivationScheme))
|
||||
if (request is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
||||
"Missing derivationScheme");
|
||||
ModelState.AddModelError(nameof(request), "Missing body");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
if (request.Config is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Config), "Missing config");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
AssertCryptoCodeWallet(paymentMethodId, out var network, out _);
|
||||
|
||||
var handler = _handlers.GetBitcoinHandler(network);
|
||||
var ctx = new PaymentMethodConfigValidationContext(_authorizationService, ModelState, request.Config, User, Store.GetPaymentMethodConfig(paymentMethodId));
|
||||
await handler.ValidatePaymentMethodConfig(ctx);
|
||||
if (ctx.MissingPermission is not null)
|
||||
{
|
||||
return this.CreateAPIPermissionError(ctx.MissingPermission.Permission, ctx.MissingPermission.Message);
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
DerivationStrategyBase strategy;
|
||||
try
|
||||
{
|
||||
strategy = network.GetDerivationSchemeParser().Parse(paymentMethodData.DerivationScheme);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
||||
"Invalid Derivation Scheme");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var settings = handler.ParsePaymentMethodConfig(ctx.Config);
|
||||
var result = GetPreviewResultData(offset, count, network, settings.AccountDerivation);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private static OnChainPaymentMethodPreviewResultData GetPreviewResultData(int offset, int count, BTCPayNetwork network, DerivationStrategyBase strategy)
|
||||
{
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
var line = strategy.GetLineFor(deposit);
|
||||
var result = new OnChainPaymentMethodPreviewResultData();
|
||||
for (var i = offset; i < amount; i++)
|
||||
for (var i = offset; i < count; i++)
|
||||
{
|
||||
var derivation = line.Derive((uint)i);
|
||||
result.Addresses.Add(
|
||||
|
@ -195,120 +137,32 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
{
|
||||
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
|
||||
Address =
|
||||
network.NBXplorerNetwork.CreateAddress(strategy,deposit.GetKeyPath((uint)i), derivation.ScriptPubKey)
|
||||
network.NBXplorerNetwork.CreateAddress(strategy, deposit.GetKeyPath((uint)i), derivation.ScriptPubKey)
|
||||
.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}")]
|
||||
public async Task<IActionResult> RemoveOnChainPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
int offset = 0, int amount = 10)
|
||||
private void AssertCryptoCodeWallet(PaymentMethodId paymentMethodId, out BTCPayNetwork network, out BTCPayWallet wallet)
|
||||
{
|
||||
AssertCryptoCodeWallet(cryptoCode, out _, out _);
|
||||
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
var store = Store;
|
||||
store.SetSupportedPaymentMethod(id, null);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
_eventAggregator.Publish(new WalletChangedEvent()
|
||||
{
|
||||
WalletId = new WalletId(storeId, cryptoCode)
|
||||
});
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}")]
|
||||
public async Task<IActionResult> UpdateOnChainPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
[FromBody] UpdateOnChainPaymentMethodRequest request)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
AssertCryptoCodeWallet(cryptoCode, out var network, out var wallet);
|
||||
|
||||
if (string.IsNullOrEmpty(request?.DerivationScheme))
|
||||
{
|
||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
||||
"Missing derivationScheme");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
try
|
||||
{
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var strategy = network.GetDerivationSchemeParser().Parse(request.DerivationScheme);
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy);
|
||||
|
||||
var dss = new DerivationSchemeSettings(strategy, network) {Label = request.Label,};
|
||||
var signing = dss.GetSigningAccountKeySettings();
|
||||
if (request.AccountKeyPath is { } r)
|
||||
{
|
||||
signing.AccountKeyPath = r.KeyPath;
|
||||
signing.RootFingerprint = r.MasterFingerprint;
|
||||
}
|
||||
else
|
||||
{
|
||||
signing.AccountKeyPath = null;
|
||||
signing.RootFingerprint = null;
|
||||
}
|
||||
|
||||
store.SetSupportedPaymentMethod(id, dss);
|
||||
storeBlob.SetExcluded(id, !request.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
_eventAggregator.Publish(new WalletChangedEvent()
|
||||
{
|
||||
WalletId = new WalletId(storeId, cryptoCode)
|
||||
});
|
||||
return Ok(GetExistingBtcLikePaymentMethod(cryptoCode, store));
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
||||
"Invalid Derivation Scheme");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertCryptoCodeWallet(string cryptoCode, out BTCPayNetwork network, out BTCPayWallet wallet)
|
||||
{
|
||||
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network is null)
|
||||
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode", "This crypto code isn't set up in this BTCPay Server instance"));
|
||||
|
||||
if (!_handlers.TryGetValue(paymentMethodId, out var h) || h is not BitcoinLikePaymentHandler handler)
|
||||
throw new JsonHttpException(this.CreateAPIError(404, "unknown-paymentMethodId", "This payment method id isn't set up in this BTCPay Server instance"));
|
||||
network = handler.Network;
|
||||
wallet = _walletProvider.GetWallet(network);
|
||||
if (wallet is null)
|
||||
throw ErrorPaymentMethodNotConfigured();
|
||||
}
|
||||
|
||||
private OnChainPaymentMethodData GetExistingBtcLikePaymentMethod(string cryptoCode, StoreData store = null)
|
||||
bool IsConfigured(PaymentMethodId paymentMethodId, [MaybeNullWhen(false)] out DerivationSchemeSettings settings)
|
||||
{
|
||||
store ??= Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
var paymentMethod = store
|
||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(method => method.PaymentId == id);
|
||||
|
||||
var excluded = storeBlob.IsExcluded(id);
|
||||
return paymentMethod == null
|
||||
? null
|
||||
: new OnChainPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.AccountDerivation.ToString(), !excluded, paymentMethod.Label,
|
||||
paymentMethod.GetSigningAccountKeySettings().GetRootedKeyPath(),
|
||||
paymentMethod.PaymentId.ToStringNormalized());
|
||||
var store = Store;
|
||||
var conf = store.GetPaymentMethodConfig(paymentMethodId);
|
||||
settings = null;
|
||||
if (conf is (null or { Type: JTokenType.Null }))
|
||||
return false;
|
||||
settings = ((BitcoinLikePaymentHandler)_handlers[paymentMethodId]).ParsePaymentMethodConfig(conf);
|
||||
return settings?.AccountDerivation is not null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,11 @@ using BTCPayServer.Data;
|
|||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -44,7 +46,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
private readonly NBXplorerDashboard _nbXplorerDashboard;
|
||||
|
@ -60,7 +62,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
public GreenfieldStoreOnChainWalletsController(
|
||||
IAuthorizationService authorizationService,
|
||||
BTCPayWalletProvider btcPayWalletProvider,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
WalletRepository walletRepository,
|
||||
ExplorerClientProvider explorerClientProvider,
|
||||
NBXplorerDashboard nbXplorerDashboard,
|
||||
|
@ -77,7 +79,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
{
|
||||
_authorizationService = authorizationService;
|
||||
_btcPayWalletProvider = btcPayWalletProvider;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
_walletRepository = walletRepository;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
PoliciesSettings = policiesSettings;
|
||||
|
@ -316,7 +318,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation);
|
||||
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId,
|
||||
utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
return Ok(utxos.Select(coin =>
|
||||
{
|
||||
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info1);
|
||||
|
@ -332,7 +334,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
Labels = info?.LegacyLabels ?? new Dictionary<string, LabelData>(),
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
Link = _transactionLinkProviders.GetTransactionLink(pmi, coin.OutPoint.ToString()),
|
||||
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, coin.OutPoint.ToString()),
|
||||
Timestamp = coin.Timestamp,
|
||||
KeyPath = coin.KeyPath,
|
||||
Confirmations = coin.Confirmations,
|
||||
|
@ -771,14 +773,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
[MaybeNullWhen(true)] out DerivationSchemeSettings derivationScheme,
|
||||
[MaybeNullWhen(false)] out IActionResult actionResult)
|
||||
{
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
derivationScheme = null;
|
||||
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network is null)
|
||||
if (!_handlers.TryGetValue(pmi, out var handler))
|
||||
{
|
||||
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode",
|
||||
"This crypto code isn't set up in this BTCPay Server instance"));
|
||||
}
|
||||
|
||||
network = ((IHasNetwork)handler).Network;
|
||||
|
||||
if (!network.WalletSupported || !_btcPayWalletProvider.IsAvailable(network))
|
||||
{
|
||||
|
@ -801,13 +803,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
private DerivationSchemeSettings? GetDerivationSchemeSettings(string cryptoCode)
|
||||
{
|
||||
var paymentMethod = Store
|
||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(p =>
|
||||
p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike &&
|
||||
p.PaymentId.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase));
|
||||
return paymentMethod;
|
||||
return Store.GetPaymentMethodConfig<DerivationSchemeSettings>(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), _handlers);
|
||||
}
|
||||
|
||||
private OnChainWalletTransactionData ToModel(WalletTransactionInfo? walletTransactionsInfoAsync,
|
||||
|
|
|
@ -6,11 +6,18 @@ using BTCPayServer.Abstractions.Constants;
|
|||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
|
@ -20,37 +27,138 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
public class GreenfieldStorePaymentMethodsController : ControllerBase
|
||||
{
|
||||
private StoreData Store => HttpContext.GetStoreData();
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public GreenfieldStorePaymentMethodsController(BTCPayNetworkProvider btcPayNetworkProvider, IAuthorizationService authorizationService)
|
||||
public GreenfieldStorePaymentMethodsController(
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
StoreRepository storeRepository,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
_storeRepository = storeRepository;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods")]
|
||||
public async Task<ActionResult<Dictionary<string, GenericPaymentMethodData>>> GetStorePaymentMethods(
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}")]
|
||||
public async Task<IActionResult> GetStorePaymentMethod(
|
||||
string storeId,
|
||||
[FromQuery] bool? enabled)
|
||||
[ModelBinder(typeof(PaymentMethodIdModelBinder))]
|
||||
PaymentMethodId paymentMethodId,
|
||||
[FromQuery] bool? includeConfig)
|
||||
{
|
||||
var result = await GetStorePaymentMethods(storeId, onlyEnabled: false, includeConfig);
|
||||
if (result is OkObjectResult { Value: GenericPaymentMethodData[] methods })
|
||||
{
|
||||
var m = methods.FirstOrDefault(m => m.PaymentMethodId == paymentMethodId.ToString());
|
||||
return m is { } ? Ok(m) : PaymentMethodNotFound();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}")]
|
||||
public async Task<IActionResult> RemoveStorePaymentMethod(
|
||||
string storeId,
|
||||
[ModelBinder(typeof(PaymentMethodIdModelBinder))]
|
||||
PaymentMethodId paymentMethodId)
|
||||
{
|
||||
AssertHasHandler(paymentMethodId);
|
||||
if (Store.GetPaymentMethodConfig(paymentMethodId) is null)
|
||||
return Ok();
|
||||
Store.SetPaymentMethodConfig(paymentMethodId, null);
|
||||
await _storeRepository.UpdateStore(Store);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}")]
|
||||
public async Task<IActionResult> UpdateStorePaymentMethod(
|
||||
string storeId,
|
||||
[ModelBinder(typeof(PaymentMethodIdModelBinder))]
|
||||
PaymentMethodId paymentMethodId,
|
||||
[FromBody] UpdatePaymentMethodRequest? request = null)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request), "Missing body");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
var handler = AssertHasHandler(paymentMethodId);
|
||||
if (request?.Config is { } config)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ctx = new PaymentMethodConfigValidationContext(_authorizationService, ModelState, config, User, Store.GetPaymentMethodConfig(paymentMethodId));
|
||||
await handler.ValidatePaymentMethodConfig(ctx);
|
||||
config = ctx.Config;
|
||||
if (ctx.MissingPermission is not null)
|
||||
{
|
||||
return this.CreateAPIPermissionError(ctx.MissingPermission.Permission, ctx.MissingPermission.Message);
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
if (ctx.StripUnknownProperties)
|
||||
config = JToken.FromObject(handler.ParsePaymentMethodConfig(config), handler.Serializer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(config), "Invalid configuration");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
Store.SetPaymentMethodConfig(paymentMethodId, config);
|
||||
}
|
||||
if (request?.Enabled is { } enabled)
|
||||
{
|
||||
var storeBlob = Store.GetStoreBlob();
|
||||
storeBlob.SetExcluded(paymentMethodId, enabled);
|
||||
Store.SetStoreBlob(storeBlob);
|
||||
}
|
||||
await _storeRepository.UpdateStore(Store);
|
||||
return await GetStorePaymentMethod(storeId, paymentMethodId, request?.Config is not null);
|
||||
}
|
||||
|
||||
private IPaymentMethodHandler AssertHasHandler(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
if (!_handlers.TryGetValue(paymentMethodId, out var handler))
|
||||
throw new JsonHttpException(PaymentMethodNotFound());
|
||||
return handler;
|
||||
}
|
||||
|
||||
private IActionResult PaymentMethodNotFound()
|
||||
{
|
||||
return this.CreateAPIError(404, "paymentmethod-not-found", "The payment method is not found");
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods")]
|
||||
public async Task<IActionResult> GetStorePaymentMethods(
|
||||
string storeId,
|
||||
[FromQuery] bool? onlyEnabled, [FromQuery] bool? includeConfig)
|
||||
{
|
||||
var storeBlob = Store.GetStoreBlob();
|
||||
var excludedPaymentMethods = storeBlob.GetExcludedPaymentMethods();
|
||||
var canModifyStore = (await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;
|
||||
;
|
||||
return Ok(Store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.Where(method =>
|
||||
enabled is null || (enabled is false && excludedPaymentMethods.Match(method.PaymentId)))
|
||||
.ToDictionary(
|
||||
method => method.PaymentId.ToStringNormalized(),
|
||||
|
||||
if (includeConfig is true)
|
||||
{
|
||||
var canModifyStore = (await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;
|
||||
if (!canModifyStore)
|
||||
return this.CreateAPIPermissionError(Policies.CanModifyStoreSettings);
|
||||
}
|
||||
|
||||
return Ok(Store.GetPaymentMethodConfigs(_handlers, onlyEnabled is true)
|
||||
.Select(
|
||||
method => new GenericPaymentMethodData()
|
||||
{
|
||||
CryptoCode = method.PaymentId.CryptoCode,
|
||||
Enabled = enabled.GetValueOrDefault(!excludedPaymentMethods.Match(method.PaymentId)),
|
||||
Data = method.PaymentId.PaymentType.GetGreenfieldData(method, canModifyStore)
|
||||
}));
|
||||
PaymentMethodId = method.Key.ToString(),
|
||||
Enabled = onlyEnabled.GetValueOrDefault(!excludedPaymentMethods.Match(method.Key)),
|
||||
Config = includeConfig is true ? JToken.FromObject(method.Value, _handlers[method.Key].Serializer.ForAPI()) : null
|
||||
}).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
|||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
@ -53,7 +54,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
{
|
||||
Stores = new[] { storeId },
|
||||
Processors = new[] { processor },
|
||||
PaymentMethods = new[] { paymentMethod }
|
||||
PaymentMethods = new[] { PaymentMethodId.Parse(paymentMethod) }
|
||||
})).FirstOrDefault();
|
||||
if (matched is null)
|
||||
{
|
||||
|
|
|
@ -124,7 +124,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
Archived = data.Archived,
|
||||
SupportUrl = storeBlob.StoreSupportUrl,
|
||||
SpeedPolicy = data.SpeedPolicy,
|
||||
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToStringNormalized(),
|
||||
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToString(),
|
||||
//blob
|
||||
//we do not include DefaultCurrencyPairs,Spread, PreferredExchange, RateScripting, RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
|
||||
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
|
||||
|
@ -163,7 +163,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
Above = criteria.Above,
|
||||
Amount = criteria.Value.Value,
|
||||
CurrencyCode = criteria.Value.Currency,
|
||||
PaymentMethod = criteria.PaymentMethod.ToStringNormalized()
|
||||
PaymentMethod = criteria.PaymentMethod.ToString()
|
||||
}).ToList() ?? new List<PaymentMethodCriteriaData>()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
|
@ -559,54 +560,21 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return result.Value ?? GetFromActionResult<T>(result.Result);
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
|
||||
bool? enabled, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(
|
||||
GetFromActionResult(GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetOnChainPaymentMethods(storeId, enabled)));
|
||||
}
|
||||
|
||||
public override Task<OnChainPaymentMethodData> GetStoreOnChainPaymentMethod(string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult(
|
||||
GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetOnChainPaymentMethod(storeId, cryptoCode)));
|
||||
}
|
||||
|
||||
public override async Task RemoveStoreOnChainPaymentMethod(string storeId, string cryptoCode,
|
||||
public override async Task<OnChainPaymentMethodPreviewResultData> PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string paymentMethodId,
|
||||
string derivationScheme, int offset = 0, int count = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(await GetController<GreenfieldStoreOnChainPaymentMethodsController>().RemoveOnChainPaymentMethod(storeId, cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task<OnChainPaymentMethodData> UpdateStoreOnChainPaymentMethod(string storeId,
|
||||
string cryptoCode, UpdateOnChainPaymentMethodRequest paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainPaymentMethodData>(
|
||||
await GetController<GreenfieldStoreOnChainPaymentMethodsController>().UpdateOnChainPaymentMethod(storeId, cryptoCode,
|
||||
new UpdateOnChainPaymentMethodRequest(
|
||||
enabled: paymentMethod.Enabled,
|
||||
label: paymentMethod.Label,
|
||||
accountKeyPath: paymentMethod.AccountKeyPath,
|
||||
derivationScheme: paymentMethod.DerivationScheme
|
||||
)));
|
||||
}
|
||||
|
||||
public override Task<OnChainPaymentMethodPreviewResultData> PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string cryptoCode,
|
||||
UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0, int amount = 10,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||
GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetProposedOnChainPaymentMethodPreview(storeId, cryptoCode,
|
||||
paymentMethod, offset, amount)));
|
||||
return GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||
await GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetProposedOnChainPaymentMethodPreview(storeId, Payments.PaymentMethodId.Parse(paymentMethodId),
|
||||
new UpdatePaymentMethodRequest() { Config = JValue.CreateString(derivationScheme) }, offset, count));
|
||||
}
|
||||
|
||||
public override Task<OnChainPaymentMethodPreviewResultData> PreviewStoreOnChainPaymentMethodAddresses(
|
||||
string storeId, string cryptoCode, int offset = 0, int amount = 10, CancellationToken token = default)
|
||||
string storeId, string paymentMethodId, int offset = 0, int amount = 10, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||
GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetOnChainPaymentMethodPreview(storeId, cryptoCode, offset,
|
||||
GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetOnChainPaymentMethodPreview(storeId, Payments.PaymentMethodId.Parse(paymentMethodId), offset,
|
||||
amount)));
|
||||
}
|
||||
|
||||
|
@ -814,71 +782,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return GetFromActionResult<StoreData>(await GetController<GreenfieldStoresController>().UpdateStore(storeId, request));
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<LNURLPayPaymentMethodData>>
|
||||
GetStoreLNURLPayPaymentMethods(string storeId, bool? enabled,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult(
|
||||
GetController<GreenfieldStoreLNURLPayPaymentMethodsController>().GetLNURLPayPaymentMethods(storeId, enabled)));
|
||||
}
|
||||
|
||||
public override Task<LNURLPayPaymentMethodData> GetStoreLNURLPayPaymentMethod(
|
||||
string storeId, string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<LNURLPayPaymentMethodData>(
|
||||
GetController<GreenfieldStoreLNURLPayPaymentMethodsController>().GetLNURLPayPaymentMethod(storeId, cryptoCode)));
|
||||
}
|
||||
|
||||
public override async Task RemoveStoreLNURLPayPaymentMethod(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(
|
||||
await GetController<GreenfieldStoreLNURLPayPaymentMethodsController>().RemoveLNURLPayPaymentMethod(storeId,
|
||||
cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task<LNURLPayPaymentMethodData> UpdateStoreLNURLPayPaymentMethod(
|
||||
string storeId, string cryptoCode,
|
||||
LNURLPayPaymentMethodData paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LNURLPayPaymentMethodData>(await
|
||||
GetController<GreenfieldStoreLNURLPayPaymentMethodsController>().UpdateLNURLPayPaymentMethod(storeId, cryptoCode,
|
||||
paymentMethod));
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<LightningNetworkPaymentMethodData>>
|
||||
GetStoreLightningNetworkPaymentMethods(string storeId, bool? enabled,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult(
|
||||
GetController<GreenfieldStoreLightningNetworkPaymentMethodsController>().GetLightningPaymentMethods(storeId, enabled)));
|
||||
}
|
||||
|
||||
public override Task<LightningNetworkPaymentMethodData> GetStoreLightningNetworkPaymentMethod(
|
||||
string storeId, string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult(
|
||||
GetController<GreenfieldStoreLightningNetworkPaymentMethodsController>().GetLightningNetworkPaymentMethod(storeId, cryptoCode)));
|
||||
}
|
||||
|
||||
public override async Task RemoveStoreLightningNetworkPaymentMethod(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(
|
||||
await GetController<GreenfieldStoreLightningNetworkPaymentMethodsController>().RemoveLightningNetworkPaymentMethod(storeId,
|
||||
cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task<LightningNetworkPaymentMethodData> UpdateStoreLightningNetworkPaymentMethod(
|
||||
string storeId, string cryptoCode,
|
||||
UpdateLightningNetworkPaymentMethodRequest paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LightningNetworkPaymentMethodData>(await
|
||||
GetController<GreenfieldStoreLightningNetworkPaymentMethodsController>().UpdateLightningNetworkPaymentMethod(storeId, cryptoCode,
|
||||
new UpdateLightningNetworkPaymentMethodRequest(paymentMethod.ConnectionString,
|
||||
paymentMethod.Enabled)));
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null,
|
||||
InvoiceStatus[] status = null,
|
||||
DateTimeOffset? startDate = null,
|
||||
|
@ -982,18 +885,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
return Task.FromResult(GetFromActionResult<PermissionMetadata[]>(GetController<UIHomeController>().Permissions()));
|
||||
}
|
||||
|
||||
public override async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId,
|
||||
bool? enabled = null, CancellationToken token = default)
|
||||
public override async Task<GenericPaymentMethodData[]> GetStorePaymentMethods(string storeId,
|
||||
bool? onlyEnabled = null, bool? includeConfig = null, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult(await GetController<GreenfieldStorePaymentMethodsController>().GetStorePaymentMethods(storeId, enabled));
|
||||
return GetFromActionResult<GenericPaymentMethodData[]>(await GetController<GreenfieldStorePaymentMethodsController>().GetStorePaymentMethods(storeId, onlyEnabled, includeConfig));
|
||||
}
|
||||
|
||||
public override async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId,
|
||||
string cryptoCode, GenerateOnChainWalletRequest request,
|
||||
public override async Task<GenerateOnChainWalletResponse> GenerateOnChainWallet(string storeId,
|
||||
string paymentMethodId, GenerateOnChainWalletRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainPaymentMethodDataWithSensitiveData>(
|
||||
await GetController<GreenfieldStoreOnChainPaymentMethodsController>().GenerateOnChainWallet(storeId, cryptoCode,
|
||||
return GetFromActionResult<GenerateOnChainWalletResponse>(
|
||||
await GetController<GreenfieldStoreOnChainPaymentMethodsController>().GenerateOnChainWallet(storeId, Payments.PaymentMethodId.Parse(paymentMethodId),
|
||||
new GenerateWalletRequest()
|
||||
{
|
||||
Passphrase = request.Passphrase,
|
||||
|
|
|
@ -6,6 +6,8 @@ using BTCPayServer.Data;
|
|||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
|
@ -41,60 +43,61 @@ namespace BTCPayServer.Controllers
|
|||
var btcpayNetwork = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var network = btcpayNetwork.NBitcoinNetwork;
|
||||
var paymentMethodId = new[] { store.GetDefaultPaymentId() }
|
||||
.Concat(store.GetEnabledPaymentIds(_NetworkProvider))
|
||||
.Concat(store.GetEnabledPaymentIds())
|
||||
.FirstOrDefault(p => p?.ToString() == request.PaymentMethodId);
|
||||
|
||||
try
|
||||
{
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var destination = paymentMethod?.GetPaymentMethodDetails().GetPaymentDestination();
|
||||
var paymentMethod = invoice.GetPaymentPrompt(paymentMethodId);
|
||||
var details = _handlers.ParsePaymentPromptDetails(paymentMethod);
|
||||
var destination = paymentMethod?.Destination;
|
||||
|
||||
switch (paymentMethod?.GetId().PaymentType)
|
||||
if (details is BitcoinPaymentPromptDetails)
|
||||
{
|
||||
case BitcoinPaymentType:
|
||||
var address = BitcoinAddress.Create(destination, network);
|
||||
var txid = (await cheater.CashCow.SendToAddressAsync(address, amount)).ToString();
|
||||
var address = BitcoinAddress.Create(destination, network);
|
||||
var txid = (await cheater.GetCashCow(cryptoCode).SendToAddressAsync(address, amount)).ToString();
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Txid = txid,
|
||||
AmountRemaining = paymentMethod.Calculate().Due - amount.ToDecimal(MoneyUnit.BTC),
|
||||
SuccessMessage = $"Created transaction {txid}"
|
||||
});
|
||||
}
|
||||
else if (details is LigthningPaymentPromptDetails)
|
||||
{
|
||||
// requires the channels to be set up using the BTCPayServer.Tests/docker-lightning-channel-setup.sh script
|
||||
var lnClient = lightningClientFactoryService.Create(
|
||||
Environment.GetEnvironmentVariable("BTCPAY_BTCEXTERNALLNDREST"),
|
||||
btcpayNetwork);
|
||||
|
||||
var lnAmount = new LightMoney(amount.Satoshi, LightMoneyUnit.Satoshi);
|
||||
var response = await lnClient.Pay(destination, new PayInvoiceParams { Amount = lnAmount });
|
||||
|
||||
if (response.Result == PayResult.Ok)
|
||||
{
|
||||
var bolt11 = BOLT11PaymentRequest.Parse(destination, network);
|
||||
var paymentHash = bolt11.PaymentHash?.ToString();
|
||||
var paid = response.Details.TotalAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
return Ok(new
|
||||
{
|
||||
Txid = txid,
|
||||
AmountRemaining = paymentMethod.Calculate().Due - amount.ToDecimal(MoneyUnit.BTC),
|
||||
SuccessMessage = $"Created transaction {txid}"
|
||||
});
|
||||
|
||||
case LightningPaymentType:
|
||||
// requires the channels to be set up using the BTCPayServer.Tests/docker-lightning-channel-setup.sh script
|
||||
var lnClient = lightningClientFactoryService.Create(
|
||||
Environment.GetEnvironmentVariable("BTCPAY_BTCEXTERNALLNDREST"),
|
||||
btcpayNetwork);
|
||||
|
||||
var lnAmount = new LightMoney(amount.Satoshi, LightMoneyUnit.Satoshi);
|
||||
var response = await lnClient.Pay(destination, new PayInvoiceParams { Amount = lnAmount });
|
||||
|
||||
if (response.Result == PayResult.Ok)
|
||||
{
|
||||
var bolt11 = BOLT11PaymentRequest.Parse(destination, network);
|
||||
var paymentHash = bolt11.PaymentHash?.ToString();
|
||||
var paid = response.Details.TotalAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
return Ok(new
|
||||
{
|
||||
Txid = paymentHash,
|
||||
AmountRemaining = paymentMethod.Calculate().TotalDue - paid,
|
||||
SuccessMessage = $"Sent payment {paymentHash}"
|
||||
});
|
||||
}
|
||||
return UnprocessableEntity(new
|
||||
{
|
||||
ErrorMessage = response.ErrorDetail
|
||||
});
|
||||
|
||||
default:
|
||||
return UnprocessableEntity(new
|
||||
{
|
||||
ErrorMessage = $"Payment method {paymentMethodId} is not supported"
|
||||
Txid = paymentHash,
|
||||
AmountRemaining = paymentMethod.Calculate().TotalDue - paid,
|
||||
SuccessMessage = $"Sent payment {paymentHash}"
|
||||
});
|
||||
}
|
||||
return UnprocessableEntity(new
|
||||
{
|
||||
ErrorMessage = response.ErrorDetail
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return UnprocessableEntity(new
|
||||
{
|
||||
ErrorMessage = $"Payment method {paymentMethodId} is not supported"
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -109,12 +112,12 @@ namespace BTCPayServer.Controllers
|
|||
[CheatModeRoute]
|
||||
public IActionResult MineBlock(string invoiceId, MineBlocksRequest request, [FromServices] Cheater cheater)
|
||||
{
|
||||
var blockRewardBitcoinAddress = cheater.CashCow.GetNewAddress();
|
||||
var blockRewardBitcoinAddress = cheater.GetCashCow(request.CryptoCode).GetNewAddress();
|
||||
try
|
||||
{
|
||||
if (request.BlockCount > 0)
|
||||
{
|
||||
cheater.CashCow.GenerateToAddress(request.BlockCount, blockRewardBitcoinAddress);
|
||||
cheater.GetCashCow(request.CryptoCode).GenerateToAddress(request.BlockCount, blockRewardBitcoinAddress);
|
||||
return Ok(new { SuccessMessage = $"Mined {request.BlockCount} block{(request.BlockCount == 1 ? "" : "s")} " });
|
||||
}
|
||||
return BadRequest(new { ErrorMessage = "Number of blocks should be at least 1" });
|
||||
|
|
|
@ -18,6 +18,8 @@ using BTCPayServer.Models.AppViewModels;
|
|||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
@ -118,7 +120,7 @@ namespace BTCPayServer.Controllers
|
|||
var additionalData = metaData
|
||||
.Where(dict => !InvoiceAdditionalDataExclude.Contains(dict.Key))
|
||||
.ToDictionary(dict => dict.Key, dict => dict.Value);
|
||||
|
||||
|
||||
var model = new InvoiceDetailsModel
|
||||
{
|
||||
StoreId = store.Id,
|
||||
|
@ -131,7 +133,6 @@ namespace BTCPayServer.Controllers
|
|||
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
|
||||
invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" :
|
||||
"low",
|
||||
RefundEmail = invoice.RefundMail,
|
||||
CreatedDate = invoice.InvoiceTime,
|
||||
ExpirationDate = invoice.ExpirationTime,
|
||||
MonitoringDate = invoice.MonitoringExpiration,
|
||||
|
@ -162,13 +163,13 @@ namespace BTCPayServer.Controllers
|
|||
model.Overpaid = details.Overpaid;
|
||||
model.StillDue = details.StillDue;
|
||||
model.HasRates = details.HasRates;
|
||||
|
||||
|
||||
if (additionalData.ContainsKey("receiptData"))
|
||||
{
|
||||
model.ReceiptData = (Dictionary<string, object>)additionalData["receiptData"];
|
||||
additionalData.Remove("receiptData");
|
||||
}
|
||||
|
||||
|
||||
if (additionalData.ContainsKey("posData") && additionalData["posData"] is string posData)
|
||||
{
|
||||
// overwrite with parsed JSON if possible
|
||||
|
@ -181,7 +182,7 @@ namespace BTCPayServer.Controllers
|
|||
additionalData["posData"] = posData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
model.AdditionalData = additionalData;
|
||||
|
||||
return View(model);
|
||||
|
@ -204,7 +205,7 @@ namespace BTCPayServer.Controllers
|
|||
if (i.RedirectURL is not null)
|
||||
{
|
||||
return Redirect(i.RedirectURL.ToString());
|
||||
}
|
||||
}
|
||||
return NotFound();
|
||||
|
||||
}
|
||||
|
@ -230,7 +231,7 @@ namespace BTCPayServer.Controllers
|
|||
JToken? receiptData = null;
|
||||
i.Metadata?.AdditionalData?.TryGetValue("receiptData", out receiptData);
|
||||
|
||||
var payments = ViewPaymentRequestViewModel.PaymentRequestInvoicePayment.GetViewModels(i, _displayFormatter, _transactionLinkProviders);
|
||||
var payments = ViewPaymentRequestViewModel.PaymentRequestInvoicePayment.GetViewModels(i, _displayFormatter, _transactionLinkProviders, _handlers);
|
||||
|
||||
vm.Amount = i.PaidAmount.Net;
|
||||
vm.Payments = receipt.ShowPayments is false ? null : payments;
|
||||
|
@ -266,12 +267,23 @@ namespace BTCPayServer.Controllers
|
|||
new { pullPaymentId = ppId });
|
||||
}
|
||||
|
||||
var paymentMethods = invoice.GetBlob(_NetworkProvider).GetPaymentMethods();
|
||||
var pmis = paymentMethods.Select(method => method.GetId()).ToList();
|
||||
pmis = pmis.Concat(pmis.Where(id => id.PaymentType == LNURLPayPaymentType.Instance)
|
||||
.Select(id => new PaymentMethodId(id.CryptoCode, LightningPaymentType.Instance))).ToList();
|
||||
var paymentMethods = invoice.GetBlob().GetPaymentPrompts();
|
||||
var pmis = paymentMethods.Select(method => method.PaymentMethodId).ToHashSet();
|
||||
// If LNURL is contained, add the LN too as a possible option
|
||||
foreach (var pmi in pmis.ToList())
|
||||
{
|
||||
if (!_handlers.TryGetValue(pmi, out var h))
|
||||
{
|
||||
pmis.Remove(pmi);
|
||||
continue;
|
||||
}
|
||||
if (h is LNURLPayPaymentHandler lh)
|
||||
{
|
||||
pmis.Add(PaymentTypes.LN.GetPaymentMethodId(lh.Network.CryptoCode));
|
||||
}
|
||||
}
|
||||
var relevant = payoutHandlers.Where(handler => pmis.Any(handler.CanHandle));
|
||||
var options = (await relevant.GetSupportedPaymentMethods(invoice.StoreData)).Where(id => pmis.Contains(id)).ToList();
|
||||
var options = (await relevant.GetSupportedPaymentMethods(invoice.StoreData)).Where(id => pmis.Contains(id)).ToHashSet();
|
||||
if (!options.Any())
|
||||
{
|
||||
var vm = new RefundModel { Title = "No matching payment method" };
|
||||
|
@ -281,15 +293,15 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
var defaultRefund = invoice.Payments
|
||||
.Select(p => p.GetBlob(_NetworkProvider))
|
||||
.Select(p => p?.GetPaymentMethodId())
|
||||
.Select(p => p.GetBlob())
|
||||
.Select(p => p.PaymentMethodId)
|
||||
.FirstOrDefault(p => p != null && options.Contains(p));
|
||||
|
||||
var refund = new RefundModel
|
||||
{
|
||||
Title = "Payment method",
|
||||
AvailablePaymentMethods =
|
||||
new SelectList(options.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString())),
|
||||
new SelectList(options.Select(id => new SelectListItem(id.ToString(), id.ToString())),
|
||||
"Value", "Text"),
|
||||
SelectedPaymentMethod = defaultRefund?.ToString() ?? options.First().ToString()
|
||||
};
|
||||
|
@ -318,71 +330,67 @@ namespace BTCPayServer.Controllers
|
|||
var store = GetCurrentStore();
|
||||
var paymentMethodId = PaymentMethodId.Parse(model.SelectedPaymentMethod);
|
||||
var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.Currency, true);
|
||||
var paymentMethodDivisibility = _CurrencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
|
||||
|
||||
RateRules rules;
|
||||
RateResult rateResult;
|
||||
CreatePullPayment createPullPayment;
|
||||
PaymentMethodAccounting accounting;
|
||||
var pms = invoice.GetPaymentMethods();
|
||||
var paymentMethod = pms.SingleOrDefault(method => method.GetId() == paymentMethodId);
|
||||
var appliedDivisibility = paymentMethodDivisibility;
|
||||
decimal dueAmount = default;
|
||||
decimal paidAmount = default;
|
||||
decimal cryptoPaid = default;
|
||||
|
||||
//TODO: Make this clean
|
||||
if (paymentMethod is null && paymentMethodId.PaymentType == LightningPaymentType.Instance)
|
||||
var pms = invoice.GetPaymentPrompts();
|
||||
if (!pms.TryGetValue(paymentMethodId, out var paymentMethod))
|
||||
{
|
||||
paymentMethod = pms[new PaymentMethodId(paymentMethodId.CryptoCode, PaymentTypes.LNURLPay)];
|
||||
// We included Lightning if only LNURL was set, so this must be LNURL
|
||||
if (_handlers.TryGetValue(paymentMethodId, out var h) && h is LightningLikePaymentHandler lnh)
|
||||
{
|
||||
pms.TryGetValue(PaymentTypes.LNURL.GetPaymentMethodId(lnh.Network.CryptoCode), out paymentMethod);
|
||||
}
|
||||
}
|
||||
if (paymentMethod is null || paymentMethod.Currency is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.SelectedPaymentMethod), $"Invalid payment method");
|
||||
return View("_RefundModal", model);
|
||||
}
|
||||
|
||||
if (paymentMethod != null)
|
||||
{
|
||||
accounting = paymentMethod.Calculate();
|
||||
cryptoPaid = accounting.Paid;
|
||||
dueAmount = accounting.TotalDue;
|
||||
paidAmount = cryptoPaid.RoundToSignificant(appliedDivisibility);
|
||||
}
|
||||
var accounting = paymentMethod.Calculate();
|
||||
decimal cryptoPaid = accounting.Paid;
|
||||
decimal dueAmount = accounting.TotalDue;
|
||||
var paymentMethodCurrency = paymentMethodId.CryptoCode;
|
||||
|
||||
var isPaidOver = invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver;
|
||||
decimal? overpaidAmount = isPaidOver ? Math.Round(paidAmount - dueAmount, appliedDivisibility) : null;
|
||||
|
||||
decimal? overpaidAmount = isPaidOver ? Math.Round(cryptoPaid - dueAmount, paymentMethod.Divisibility) : null;
|
||||
int ppDivisibility = paymentMethod.Divisibility;
|
||||
switch (model.RefundStep)
|
||||
{
|
||||
case RefundSteps.SelectPaymentMethod:
|
||||
model.RefundStep = RefundSteps.SelectRate;
|
||||
model.Title = "How much to refund?";
|
||||
|
||||
if (paymentMethod != null && cryptoPaid != default)
|
||||
var paidCurrency = Math.Round(cryptoPaid * paymentMethod.Rate, cdCurrency.Divisibility);
|
||||
model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethod.Divisibility);
|
||||
model.RateThenText = _displayFormatter.Currency(model.CryptoAmountThen, paymentMethodCurrency);
|
||||
rules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
||||
rateResult = await _RateProvider.FetchRate(
|
||||
new CurrencyPair(paymentMethodCurrency, invoice.Currency), rules,
|
||||
cancellationToken);
|
||||
//TODO: What if fetching rate failed?
|
||||
if (rateResult.BidAsk is null)
|
||||
{
|
||||
var paidCurrency = Math.Round(cryptoPaid * paymentMethod.Rate, cdCurrency.Divisibility);
|
||||
model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethodDivisibility);
|
||||
model.RateThenText = _displayFormatter.Currency(model.CryptoAmountThen, paymentMethodId.CryptoCode);
|
||||
rules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
||||
rateResult = await _RateProvider.FetchRate(
|
||||
new CurrencyPair(paymentMethodId.CryptoCode, invoice.Currency), rules,
|
||||
cancellationToken);
|
||||
//TODO: What if fetching rate failed?
|
||||
if (rateResult.BidAsk is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.SelectedRefundOption),
|
||||
$"Impossible to fetch rate: {rateResult.EvaluatedRule}");
|
||||
return View("_RefundModal", model);
|
||||
}
|
||||
|
||||
model.CryptoAmountNow = Math.Round(paidCurrency / rateResult.BidAsk.Bid, paymentMethodDivisibility);
|
||||
model.CurrentRateText = _displayFormatter.Currency(model.CryptoAmountNow, paymentMethodId.CryptoCode);
|
||||
model.FiatAmount = paidCurrency;
|
||||
ModelState.AddModelError(nameof(model.SelectedRefundOption),
|
||||
$"Impossible to fetch rate: {rateResult.EvaluatedRule}");
|
||||
return View("_RefundModal", model);
|
||||
}
|
||||
model.CryptoCode = paymentMethodId.CryptoCode;
|
||||
model.CryptoDivisibility = paymentMethodDivisibility;
|
||||
|
||||
model.CryptoAmountNow = Math.Round(paidCurrency / rateResult.BidAsk.Bid, paymentMethod.Divisibility);
|
||||
model.CurrentRateText = _displayFormatter.Currency(model.CryptoAmountNow, paymentMethodCurrency);
|
||||
model.FiatAmount = paidCurrency;
|
||||
|
||||
model.CryptoCode = paymentMethodCurrency;
|
||||
model.CryptoDivisibility = paymentMethod.Divisibility;
|
||||
model.InvoiceDivisibility = cdCurrency.Divisibility;
|
||||
model.InvoiceCurrency = invoice.Currency;
|
||||
model.CustomAmount = model.FiatAmount;
|
||||
model.CustomCurrency = invoice.Currency;
|
||||
model.SubtractPercentage = 0;
|
||||
model.OverpaidAmount = overpaidAmount;
|
||||
model.OverpaidAmountText = overpaidAmount != null ? _displayFormatter.Currency(overpaidAmount.Value, paymentMethodId.CryptoCode) : null;
|
||||
model.OverpaidAmountText = overpaidAmount != null ? _displayFormatter.Currency(overpaidAmount.Value, paymentMethodCurrency) : null;
|
||||
model.FiatText = _displayFormatter.Currency(model.FiatAmount, invoice.Currency);
|
||||
return View("_RefundModal", model);
|
||||
|
||||
|
@ -405,32 +413,32 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
return View("_RefundModal", model);
|
||||
}
|
||||
|
||||
|
||||
switch (model.SelectedRefundOption)
|
||||
{
|
||||
case "RateThen":
|
||||
createPullPayment.Currency = paymentMethodId.CryptoCode;
|
||||
createPullPayment.Currency = paymentMethodCurrency;
|
||||
createPullPayment.Amount = model.CryptoAmountThen;
|
||||
createPullPayment.AutoApproveClaims = authorizedForAutoApprove;
|
||||
break;
|
||||
|
||||
case "CurrentRate":
|
||||
createPullPayment.Currency = paymentMethodId.CryptoCode;
|
||||
createPullPayment.Currency = paymentMethodCurrency;
|
||||
createPullPayment.Amount = model.CryptoAmountNow;
|
||||
createPullPayment.AutoApproveClaims = authorizedForAutoApprove;
|
||||
break;
|
||||
|
||||
case "Fiat":
|
||||
appliedDivisibility = cdCurrency.Divisibility;
|
||||
ppDivisibility = cdCurrency.Divisibility;
|
||||
createPullPayment.Currency = invoice.Currency;
|
||||
createPullPayment.Amount = model.FiatAmount;
|
||||
createPullPayment.AutoApproveClaims = false;
|
||||
break;
|
||||
|
||||
|
||||
case "OverpaidAmount":
|
||||
model.Title = "How much to refund?";
|
||||
model.RefundStep = RefundSteps.SelectRate;
|
||||
|
||||
|
||||
if (!isPaidOver)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.SelectedRefundOption), "Invoice is not overpaid");
|
||||
|
@ -443,8 +451,8 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
return View("_RefundModal", model);
|
||||
}
|
||||
|
||||
createPullPayment.Currency = paymentMethodId.CryptoCode;
|
||||
|
||||
createPullPayment.Currency = paymentMethodCurrency;
|
||||
createPullPayment.Amount = overpaidAmount!.Value;
|
||||
createPullPayment.AutoApproveClaims = true;
|
||||
break;
|
||||
|
@ -469,7 +477,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
rules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
||||
rateResult = await _RateProvider.FetchRate(
|
||||
new CurrencyPair(paymentMethodId.CryptoCode, model.CustomCurrency), rules,
|
||||
new CurrencyPair(paymentMethodCurrency, model.CustomCurrency), rules,
|
||||
cancellationToken);
|
||||
|
||||
//TODO: What if fetching rate failed?
|
||||
|
@ -482,7 +490,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
createPullPayment.Currency = model.CustomCurrency;
|
||||
createPullPayment.Amount = model.CustomAmount;
|
||||
createPullPayment.AutoApproveClaims = authorizedForAutoApprove && paymentMethodId.CryptoCode == model.CustomCurrency;
|
||||
createPullPayment.AutoApproveClaims = authorizedForAutoApprove && paymentMethodCurrency == model.CustomCurrency;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -499,7 +507,7 @@ namespace BTCPayServer.Controllers
|
|||
if (model.SubtractPercentage is > 0 and <= 100)
|
||||
{
|
||||
var reduceByAmount = createPullPayment.Amount * (model.SubtractPercentage / 100);
|
||||
createPullPayment.Amount = Math.Round(createPullPayment.Amount - reduceByAmount, appliedDivisibility);
|
||||
createPullPayment.Amount = Math.Round(createPullPayment.Amount - reduceByAmount, ppDivisibility);
|
||||
}
|
||||
|
||||
var ppId = await _paymentHostedService.CreatePullPayment(createPullPayment);
|
||||
|
@ -531,30 +539,33 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
Archived = invoice.Archived,
|
||||
Payments = invoice.GetPayments(false),
|
||||
CryptoPayments = invoice.GetPaymentMethods().Select(
|
||||
CryptoPayments = invoice.GetPaymentPrompts().Select(
|
||||
data =>
|
||||
{
|
||||
var accounting = data.Calculate();
|
||||
var paymentMethodId = data.GetId();
|
||||
var hasPayment = accounting.CryptoPaid > 0;
|
||||
var paymentMethodId = data.PaymentMethodId;
|
||||
var hasPayment = accounting.PaymentMethodPaid > 0;
|
||||
var overpaidAmount = accounting.OverpaidHelper;
|
||||
var rate = ExchangeRate(data.GetId().CryptoCode, data);
|
||||
|
||||
if (rate is not null) hasRates = true;
|
||||
if (hasPayment && overpaidAmount > 0) overpaid = true;
|
||||
if (hasPayment && accounting.Due > 0) stillDue = true;
|
||||
var rate = ExchangeRate(data.Currency, data);
|
||||
|
||||
if (rate is not null)
|
||||
hasRates = true;
|
||||
if (hasPayment && overpaidAmount > 0)
|
||||
overpaid = true;
|
||||
if (hasPayment && accounting.Due > 0)
|
||||
stillDue = true;
|
||||
|
||||
return new InvoiceDetailsModel.CryptoPayment
|
||||
{
|
||||
Rate = rate,
|
||||
PaymentMethodRaw = data,
|
||||
PaymentMethodId = paymentMethodId,
|
||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||
TotalDue = _displayFormatter.Currency(accounting.TotalDue, paymentMethodId.CryptoCode),
|
||||
Due = hasPayment ? _displayFormatter.Currency(accounting.Due, paymentMethodId.CryptoCode) : null,
|
||||
Paid = hasPayment ? _displayFormatter.Currency(accounting.CryptoPaid, paymentMethodId.CryptoCode) : null,
|
||||
Overpaid = hasPayment ? _displayFormatter.Currency(overpaidAmount, paymentMethodId.CryptoCode) : null,
|
||||
Address = data.GetPaymentMethodDetails().GetPaymentDestination()
|
||||
PaymentMethod = paymentMethodId.ToString(),
|
||||
TotalDue = _displayFormatter.Currency(accounting.TotalDue, data.Currency),
|
||||
Due = hasPayment ? _displayFormatter.Currency(accounting.Due, data.Currency) : null,
|
||||
Paid = hasPayment ? _displayFormatter.Currency(accounting.PaymentMethodPaid, data.Currency) : null,
|
||||
Overpaid = hasPayment ? _displayFormatter.Currency(overpaidAmount, data.Currency) : null,
|
||||
Address = data.Destination
|
||||
};
|
||||
}).ToList(),
|
||||
Overpaid = overpaid,
|
||||
|
@ -620,11 +631,12 @@ namespace BTCPayServer.Controllers
|
|||
if (!GetCurrentStore().HasPermission(GetUserId(), Policies.CanModifyStoreSettings))
|
||||
return Forbid();
|
||||
|
||||
var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_NetworkProvider, network.CryptoCode))?.AccountDerivation;
|
||||
var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_handlers, network.CryptoCode))?.AccountDerivation;
|
||||
if (derivationScheme is null)
|
||||
return NotSupported("This feature is only available to BTC wallets");
|
||||
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var bumpableAddresses = (await GetAddresses(selectedItems))
|
||||
.Where(p => p.GetPaymentMethodId().IsBTCOnChain)
|
||||
.Where(p => p.GetPaymentMethodId() == btc)
|
||||
.Select(p => p.GetAddress()).ToHashSet();
|
||||
var utxos = await explorer.GetUTXOsAsync(derivationScheme);
|
||||
var bumpableUTXOs = utxos.GetUnspentUTXOs().Where(u => u.Confirmations == 0 && bumpableAddresses.Contains(u.ScriptPubKey.Hash.ToString())).ToArray();
|
||||
|
@ -706,12 +718,13 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
bool isDefaultPaymentId = false;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var btcId = PaymentMethodId.Parse("BTC");
|
||||
var lnId = PaymentMethodId.Parse("BTC_LightningLike");
|
||||
var lnurlId = PaymentMethodId.Parse("BTC_LNURLPAY");
|
||||
|
||||
var displayedPaymentMethods = invoice.GetPaymentPrompts().Select(p => p.PaymentMethodId).ToList();
|
||||
|
||||
var displayedPaymentMethods = invoice.GetPaymentMethods().Select(p => p.GetId()).ToList();
|
||||
|
||||
var btcId = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||
var lnurlId = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
|
||||
var lnId = PaymentTypes.LN.GetPaymentMethodId("BTC");
|
||||
|
||||
// Exclude Lightning if OnChainWithLnInvoiceFallback is active and we have both payment methods
|
||||
if (storeBlob is { OnChainWithLnInvoiceFallback: true } &&
|
||||
|
@ -729,11 +742,12 @@ namespace BTCPayServer.Controllers
|
|||
if (displayedPaymentMethods.Contains(lnId) && displayedPaymentMethods.Contains(lnurlId))
|
||||
displayedPaymentMethods.Remove(lnurlId);
|
||||
|
||||
|
||||
if (paymentMethodId is not null && !displayedPaymentMethods.Contains(paymentMethodId))
|
||||
paymentMethodId = null;
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
PaymentMethodId? invoicePaymentId = invoice.GetDefaultPaymentMethod();
|
||||
PaymentMethodId? invoicePaymentId = invoice.DefaultPaymentMethod;
|
||||
PaymentMethodId? storePaymentId = store.GetDefaultPaymentId();
|
||||
if (invoicePaymentId is not null)
|
||||
{
|
||||
|
@ -755,56 +769,55 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
|
||||
displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ??
|
||||
var defaultBTC = PaymentTypes.CHAIN.GetPaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode);
|
||||
var defaultLNURLPay = PaymentTypes.LNURL.GetPaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode);
|
||||
paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e == defaultBTC) ??
|
||||
displayedPaymentMethods.FirstOrDefault(e => e == defaultLNURLPay) ??
|
||||
displayedPaymentMethods.FirstOrDefault();
|
||||
}
|
||||
isDefaultPaymentId = true;
|
||||
}
|
||||
if (paymentMethodId is null)
|
||||
return null;
|
||||
|
||||
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
|
||||
if (network is null || !invoice.Support(paymentMethodId))
|
||||
if (!invoice.Support(paymentMethodId))
|
||||
{
|
||||
if (!isDefaultPaymentId)
|
||||
return null;
|
||||
var paymentMethodTemp = invoice
|
||||
.GetPaymentMethods()
|
||||
.Where(p => displayedPaymentMethods.Contains(p.GetId()))
|
||||
.GetPaymentPrompts()
|
||||
.Where(p => displayedPaymentMethods.Contains(p.PaymentMethodId))
|
||||
.FirstOrDefault();
|
||||
if (paymentMethodTemp is null)
|
||||
return null;
|
||||
network = paymentMethodTemp.Network;
|
||||
paymentMethodId = paymentMethodTemp.GetId();
|
||||
paymentMethodId = paymentMethodTemp.PaymentMethodId;
|
||||
}
|
||||
|
||||
if (!_handlers.TryGetValue(paymentMethodId, out var handler))
|
||||
return null;
|
||||
|
||||
// We activate the default payment method, and also those which aren't displayed (as they can't be set as default)
|
||||
bool activated = false;
|
||||
foreach (var pm in invoice.GetPaymentMethods())
|
||||
PaymentPrompt? prompt = null;
|
||||
foreach (var pm in invoice.GetPaymentPrompts())
|
||||
{
|
||||
var pmi = pm.GetId();
|
||||
var pmi = pm.PaymentMethodId;
|
||||
if (pmi == paymentMethodId)
|
||||
prompt = pm;
|
||||
if (pmi != paymentMethodId || !displayedPaymentMethods.Contains(pmi))
|
||||
continue;
|
||||
var pmd = pm.GetPaymentMethodDetails();
|
||||
if (!pmd.Activated)
|
||||
if (!pm.Activated)
|
||||
{
|
||||
if (await _invoiceActivator.ActivateInvoicePaymentMethod(pmi, invoice, store))
|
||||
if (await _invoiceActivator.ActivateInvoicePaymentMethod(invoice.Id, pmi))
|
||||
{
|
||||
activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prompt is null)
|
||||
return null;
|
||||
if (activated)
|
||||
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
||||
|
||||
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
var dto = invoice.EntityToDTO();
|
||||
var accounting = paymentMethod.Calculate();
|
||||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
||||
var accounting = prompt.Calculate();
|
||||
|
||||
switch (lang?.ToLowerInvariant())
|
||||
{
|
||||
|
@ -841,10 +854,21 @@ namespace BTCPayServer.Controllers
|
|||
.Replace("{InvoiceId}", Uri.EscapeDataString(invoice.Id))
|
||||
: null;
|
||||
|
||||
string GetPaymentMethodName(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
_paymentModelExtensions.TryGetValue(paymentMethodId, out var extension);
|
||||
return extension?.DisplayName ?? paymentMethodId.ToString();
|
||||
}
|
||||
string GetPaymentMethodImage(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
_paymentModelExtensions.TryGetValue(paymentMethodId, out var extension);
|
||||
return extension?.Image ?? "";
|
||||
}
|
||||
var model = new PaymentModel
|
||||
{
|
||||
Activated = paymentMethodDetails.Activated,
|
||||
CryptoCode = network.CryptoCode,
|
||||
Activated = prompt.Activated,
|
||||
PaymentMethodName = GetPaymentMethodName(paymentMethodId),
|
||||
CryptoCode = prompt.Currency,
|
||||
RootPath = Request.PathBase.Value.WithTrailingSlash(),
|
||||
OrderId = orderId,
|
||||
InvoiceId = invoiceId,
|
||||
|
@ -858,21 +882,21 @@ namespace BTCPayServer.Controllers
|
|||
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
|
||||
CelebratePayment = storeBlob.CelebratePayment,
|
||||
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback,
|
||||
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
|
||||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
||||
CryptoImage = Request.GetRelativePathOrAbsolute(GetPaymentMethodImage(paymentMethodId)),
|
||||
BtcAddress = prompt.Destination,
|
||||
BtcDue = accounting.ShowMoney(accounting.Due),
|
||||
BtcPaid = accounting.ShowMoney(accounting.Paid),
|
||||
InvoiceCurrency = invoice.Currency,
|
||||
OrderAmount = accounting.ShowMoney(accounting.TotalDue - accounting.NetworkFee),
|
||||
OrderAmount = accounting.ShowMoney(accounting.TotalDue - accounting.PaymentMethodFee),
|
||||
IsUnsetTopUp = invoice.IsUnsetTopUp(),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
CustomerEmail = invoice.Metadata.BuyerEmail,
|
||||
RequiresRefundEmail = invoice.RequiresRefundEmail ?? storeBlob.RequiresRefundEmail,
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
DisplayExpirationTimer = (int)storeBlob.DisplayExpirationTimer.TotalSeconds,
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
||||
ItemDesc = invoice.Metadata.ItemDesc,
|
||||
Rate = ExchangeRate(network.CryptoCode, paymentMethod, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
Rate = ExchangeRate(prompt.Currency, prompt, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
MerchantRefLink = invoice.RedirectURL?.AbsoluteUri ?? receiptUrl ?? "/",
|
||||
ReceiptLink = receiptUrl,
|
||||
RedirectAutomatically = invoice.RedirectAutomatically,
|
||||
|
@ -894,48 +918,54 @@ namespace BTCPayServer.Controllers
|
|||
SpeedPolicy.LowSpeed => 6,
|
||||
_ => null
|
||||
},
|
||||
ReceivedConfirmations = invoice.GetAllBitcoinPaymentData(false).FirstOrDefault()?.ConfirmationCount,
|
||||
ReceivedConfirmations = handler is BitcoinLikePaymentHandler bh ? invoice.GetAllBitcoinPaymentData(bh, false).FirstOrDefault()?.ConfirmationCount : null,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Status = invoice.StatusString,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
|
||||
IsMultiCurrency = invoice.GetPayments(false).Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
||||
NetworkFee = prompt.PaymentMethodFee,
|
||||
IsMultiCurrency = invoice.GetPayments(false).Select(p => p.PaymentMethodId).Concat(new[] { prompt.PaymentMethodId }).Distinct().Count() > 1,
|
||||
StoreId = store.Id,
|
||||
AvailableCryptos = invoice.GetPaymentMethods()
|
||||
AvailableCryptos = invoice.GetPaymentPrompts()
|
||||
.Select(kv =>
|
||||
{
|
||||
var availableCryptoPaymentMethodId = kv.GetId();
|
||||
var availableCryptoHandler = _paymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
|
||||
var pmName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId);
|
||||
var handler = _handlers[kv.PaymentMethodId];
|
||||
var pmName = GetPaymentMethodName(kv.PaymentMethodId);
|
||||
return new PaymentModel.AvailableCrypto
|
||||
{
|
||||
Displayed = displayedPaymentMethods.Contains(kv.GetId()),
|
||||
PaymentMethodId = kv.GetId().ToString(),
|
||||
CryptoCode = kv.Network?.CryptoCode ?? kv.GetId().CryptoCode,
|
||||
Displayed = displayedPaymentMethods.Contains(kv.PaymentMethodId),
|
||||
PaymentMethodId = kv.PaymentMethodId.ToString(),
|
||||
CryptoCode = kv.Currency,
|
||||
PaymentMethodName = isAltcoinsBuild
|
||||
? pmName
|
||||
: pmName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", ""),
|
||||
IsLightning =
|
||||
kv.GetId().PaymentType == PaymentTypes.LightningLike,
|
||||
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
|
||||
IsLightning = handler is ILightningPaymentHandler,
|
||||
CryptoImage = Request.GetRelativePathOrAbsolute(GetPaymentMethodImage(kv.PaymentMethodId)),
|
||||
Link = Url.Action(nameof(Checkout),
|
||||
new
|
||||
{
|
||||
invoiceId,
|
||||
paymentMethodId = kv.GetId().ToString()
|
||||
paymentMethodId = kv.PaymentMethodId.ToString()
|
||||
})
|
||||
};
|
||||
}).Where(c => c.CryptoImage != "/")
|
||||
.OrderByDescending(a => a.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode).ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob, paymentMethod);
|
||||
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
||||
if (_paymentModelExtensions.TryGetValue(paymentMethodId, out var extension))
|
||||
extension.ModifyPaymentModel(new PaymentModelContext(model, store, storeBlob, invoice, Url, prompt, handler));
|
||||
model.UISettings = _viewProvider.TryGetViewViewModel(prompt, "CheckoutUI")?.View as CheckoutUIPaymentMethodSettings;
|
||||
model.PaymentMethodId = paymentMethodId.ToString();
|
||||
model.PaymentType = paymentMethodId.PaymentType.ToString();
|
||||
model.OrderAmountFiat = OrderAmountFromInvoice(model.CryptoCode, invoice, DisplayFormatter.CurrencyFormat.Symbol);
|
||||
|
||||
foreach (var paymentPrompt in invoice.GetPaymentPrompts())
|
||||
{
|
||||
var vvm = _viewProvider.TryGetViewViewModel(paymentPrompt, "CheckoutUI");
|
||||
if (vvm?.View is CheckoutUIPaymentMethodSettings { ExtensionPartial: { } partial })
|
||||
{
|
||||
model.ExtensionPartials.Add(partial);
|
||||
}
|
||||
}
|
||||
|
||||
if (storeBlob.PlaySoundOnPayment)
|
||||
{
|
||||
model.PaymentSoundUrl = string.IsNullOrEmpty(storeBlob.SoundFileId)
|
||||
|
@ -944,7 +974,7 @@ namespace BTCPayServer.Controllers
|
|||
model.ErrorSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout-v2/error.mp3");
|
||||
model.NfcReadSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout-v2/nfcread.mp3");
|
||||
}
|
||||
|
||||
|
||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||
model.TimeLeft = expiration.PrettyPrint();
|
||||
return model;
|
||||
|
@ -962,7 +992,7 @@ namespace BTCPayServer.Controllers
|
|||
return _displayFormatter.Currency(invoiceEntity.Price, currency, format);
|
||||
}
|
||||
|
||||
private string? ExchangeRate(string cryptoCode, PaymentMethod paymentMethod, DisplayFormatter.CurrencyFormat format = DisplayFormatter.CurrencyFormat.Code)
|
||||
private string? ExchangeRate(string cryptoCode, PaymentPrompt paymentMethod, DisplayFormatter.CurrencyFormat format = DisplayFormatter.CurrencyFormat.Code)
|
||||
{
|
||||
var currency = paymentMethod.ParentEntity.Currency;
|
||||
var crypto = cryptoCode.ToUpperInvariant(); // uppercase to make comparison easier, might be "sats"
|
||||
|
@ -1081,7 +1111,7 @@ namespace BTCPayServer.Controllers
|
|||
invoiceQuery.Take = model.Count;
|
||||
invoiceQuery.Skip = model.Skip;
|
||||
invoiceQuery.IncludeRefunds = true;
|
||||
|
||||
|
||||
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
||||
|
||||
// Apps
|
||||
|
@ -1157,12 +1187,12 @@ namespace BTCPayServer.Controllers
|
|||
var store = await _StoreRepository.FindStore(model.StoreId);
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
if (!store.AnyPaymentMethodAvailable(_NetworkProvider))
|
||||
|
||||
if (!store.AnyPaymentMethodAvailable())
|
||||
{
|
||||
return NoPaymentMethodResult(store.Id);
|
||||
}
|
||||
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new CreateInvoiceModel
|
||||
{
|
||||
|
@ -1182,11 +1212,11 @@ namespace BTCPayServer.Controllers
|
|||
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (!store.AnyPaymentMethodAvailable(_NetworkProvider))
|
||||
if (!store.AnyPaymentMethodAvailable())
|
||||
{
|
||||
return NoPaymentMethodResult(store.Id);
|
||||
}
|
||||
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
model.CheckoutType = storeBlob.CheckoutType;
|
||||
model.AvailablePaymentMethods = GetPaymentMethodsSelectList(store);
|
||||
|
@ -1203,7 +1233,7 @@ namespace BTCPayServer.Controllers
|
|||
ModelState.AddModelError(nameof(model.Metadata), "Metadata was not valid JSON");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
|
@ -1231,7 +1261,7 @@ namespace BTCPayServer.Controllers
|
|||
Amount = model.Amount,
|
||||
Currency = model.Currency,
|
||||
Metadata = metadata.ToJObject(),
|
||||
Checkout = new ()
|
||||
Checkout = new()
|
||||
{
|
||||
RedirectURL = store.StoreWebsite,
|
||||
DefaultPaymentMethod = model.DefaultPaymentMethod,
|
||||
|
@ -1338,7 +1368,7 @@ namespace BTCPayServer.Controllers
|
|||
? ParsePosData(items[i])
|
||||
: items[i].ToString());
|
||||
}
|
||||
|
||||
|
||||
result.TryAdd(item.Key, arrayResult);
|
||||
|
||||
break;
|
||||
|
@ -1357,9 +1387,9 @@ namespace BTCPayServer.Controllers
|
|||
private SelectList GetPaymentMethodsSelectList(StoreData store)
|
||||
{
|
||||
var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
return new SelectList(store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId))
|
||||
.Select(method => new SelectListItem(method.PaymentId.ToPrettyString(), method.PaymentId.ToString())),
|
||||
return new SelectList(store.GetPaymentMethodConfigs()
|
||||
.Where(s => !excludeFilter.Match(s.Key))
|
||||
.Select(method => new SelectListItem(method.Key.ToString(), method.Key.ToString())),
|
||||
nameof(SelectListItem.Value),
|
||||
nameof(SelectListItem.Text));
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ using Microsoft.AspNetCore.Routing;
|
|||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
using Serilog.Filters;
|
||||
using PeterO.Numbers;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
|
@ -47,7 +49,7 @@ namespace BTCPayServer.Controllers
|
|||
private readonly DisplayFormatter _displayFormatter;
|
||||
readonly EventAggregator _EventAggregator;
|
||||
readonly BTCPayNetworkProvider _NetworkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly PullPaymentHostedService _paymentHostedService;
|
||||
private readonly LanguageService _languageService;
|
||||
|
@ -57,6 +59,8 @@ namespace BTCPayServer.Controllers
|
|||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
||||
private readonly PaymentMethodViewProvider _viewProvider;
|
||||
private readonly AppService _appService;
|
||||
private readonly IFileService _fileService;
|
||||
|
||||
|
@ -85,7 +89,9 @@ namespace BTCPayServer.Controllers
|
|||
AppService appService,
|
||||
IFileService fileService,
|
||||
IAuthorizationService authorizationService,
|
||||
TransactionLinkProviders transactionLinkProviders)
|
||||
TransactionLinkProviders transactionLinkProviders,
|
||||
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||
PaymentMethodViewProvider viewProvider)
|
||||
{
|
||||
_displayFormatter = displayFormatter;
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
|
@ -96,7 +102,7 @@ namespace BTCPayServer.Controllers
|
|||
_UserManager = userManager;
|
||||
_EventAggregator = eventAggregator;
|
||||
_NetworkProvider = networkProvider;
|
||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
||||
_handlers = paymentMethodHandlerDictionary;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_paymentHostedService = paymentHostedService;
|
||||
WebhookNotificationManager = webhookNotificationManager;
|
||||
|
@ -107,6 +113,8 @@ namespace BTCPayServer.Controllers
|
|||
_linkGenerator = linkGenerator;
|
||||
_authorizationService = authorizationService;
|
||||
_transactionLinkProviders = transactionLinkProviders;
|
||||
_paymentModelExtensions = paymentModelExtensions;
|
||||
_viewProvider = viewProvider;
|
||||
_fileService = fileService;
|
||||
_appService = appService;
|
||||
}
|
||||
|
@ -126,7 +134,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(id),
|
||||
PaymentRequestId = id,
|
||||
BuyerEmail = invoiceMetadata.TryGetValue("buyerEmail", out var formEmail) && formEmail.Type == JTokenType.String ? formEmail.Value<string>():
|
||||
BuyerEmail = invoiceMetadata.TryGetValue("buyerEmail", out var formEmail) && formEmail.Type == JTokenType.String ? formEmail.Value<string>() :
|
||||
string.IsNullOrEmpty(prBlob.Email) ? null : prBlob.Email
|
||||
}.ToJObject(), new JsonMergeSettings() { MergeNullValueHandling = MergeNullValueHandling.Ignore });
|
||||
|
||||
|
@ -169,7 +177,10 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy;
|
||||
entity.DefaultLanguage = invoice.Checkout.DefaultLanguage;
|
||||
entity.DefaultPaymentMethod = invoice.Checkout.DefaultPaymentMethod ?? store.GetDefaultPaymentId()?.ToStringNormalized() ?? new PaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode, PaymentTypes.BTCLike).ToStringNormalized();
|
||||
if (invoice.Checkout.DefaultPaymentMethod is not null && PaymentMethodId.TryParse(invoice.Checkout.DefaultPaymentMethod, out var paymentMethodId))
|
||||
{
|
||||
entity.DefaultPaymentMethod = paymentMethodId;
|
||||
}
|
||||
entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
|
||||
entity.CheckoutType = invoice.Checkout.CheckoutType;
|
||||
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
|
||||
|
@ -217,77 +228,29 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
var getAppsTaggingStore = _InvoiceRepository.GetAppsTaggingStore(store.Id);
|
||||
|
||||
if (entity.Metadata.BuyerEmail != null)
|
||||
{
|
||||
if (!MailboxAddressValidator.IsMailboxAddress(entity.Metadata.BuyerEmail))
|
||||
throw new BitpayHttpException(400, "Invalid email");
|
||||
entity.RefundMail = entity.Metadata.BuyerEmail;
|
||||
}
|
||||
entity.Status = InvoiceStatusLegacy.New;
|
||||
entity.UpdateTotals();
|
||||
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
||||
var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any()
|
||||
if (invoicePaymentMethodFilter != null)
|
||||
|
||||
|
||||
var creationContext = new InvoiceCreationContext(store, storeBlob, entity, logs, _handlers, invoicePaymentMethodFilter);
|
||||
creationContext.SetLazyActivation(entity.LazyPaymentMethods);
|
||||
foreach (var term in additionalSearchTerms ?? Array.Empty<string>())
|
||||
creationContext.AdditionalSearchTerms.Add(term);
|
||||
|
||||
if (entity.Type == InvoiceType.TopUp || entity.Price != 0m)
|
||||
{
|
||||
excludeFilter = PaymentFilter.Or(excludeFilter,
|
||||
invoicePaymentMethodFilter);
|
||||
}
|
||||
foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId))
|
||||
.Select(c => _NetworkProvider.GetNetwork<BTCPayNetworkBase>(c.PaymentId.CryptoCode))
|
||||
.Where(c => c != null))
|
||||
{
|
||||
currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, entity.Currency));
|
||||
foreach (var paymentMethodCriteria in storeBlob.PaymentMethodCriteria)
|
||||
{
|
||||
if (paymentMethodCriteria.Value != null)
|
||||
{
|
||||
currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, paymentMethodCriteria.Value.Currency));
|
||||
}
|
||||
}
|
||||
}
|
||||
await creationContext.BeforeFetchingRates();
|
||||
await FetchRates(creationContext, cancellationToken);
|
||||
|
||||
var rateRules = storeBlob.GetRateRules(_NetworkProvider);
|
||||
var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules, cancellationToken);
|
||||
var fetchingAll = WhenAllFetched(logs, fetchingByCurrencyPair);
|
||||
|
||||
List<ISupportedPaymentMethod> supported = new List<ISupportedPaymentMethod>();
|
||||
var paymentMethods = new PaymentMethodDictionary();
|
||||
|
||||
bool noNeedForMethods = entity.Type != InvoiceType.TopUp && entity.Price == 0m;
|
||||
|
||||
if (!noNeedForMethods)
|
||||
{
|
||||
// This loop ends with .ToList so we are querying all payment methods at once
|
||||
// instead of sequentially to improve response time
|
||||
var x1 = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId) &&
|
||||
_paymentMethodHandlerDictionary.Support(s.PaymentId))
|
||||
.Select(c =>
|
||||
(Handler: _paymentMethodHandlerDictionary[c.PaymentId],
|
||||
SupportedPaymentMethod: c,
|
||||
Network: _NetworkProvider.GetNetwork<BTCPayNetworkBase>(c.PaymentId.CryptoCode)))
|
||||
.Where(c => c.Network != null).ToList();
|
||||
var pmis = x1.Select(tuple => tuple.SupportedPaymentMethod.PaymentId).ToHashSet();
|
||||
foreach (var o in x1
|
||||
.Select(o =>
|
||||
(SupportedPaymentMethod: o.SupportedPaymentMethod,
|
||||
PaymentMethod: CreatePaymentMethodAsync(fetchingByCurrencyPair, o.Handler,
|
||||
o.SupportedPaymentMethod, o.Network, entity, store, logs, pmis)))
|
||||
.ToList())
|
||||
{
|
||||
var paymentMethod = await o.PaymentMethod;
|
||||
if (paymentMethod == null)
|
||||
continue;
|
||||
supported.Add(o.SupportedPaymentMethod);
|
||||
paymentMethods.Add(paymentMethod);
|
||||
}
|
||||
|
||||
if (supported.Count == 0)
|
||||
await creationContext.CreatePaymentPrompts();
|
||||
var contexts = creationContext.PaymentMethodContexts
|
||||
.Where(s => s.Value.Status is PaymentMethodContext.ContextStatus.WaitingForActivation or PaymentMethodContext.ContextStatus.Created)
|
||||
.Select(s => s.Value)
|
||||
.ToList();
|
||||
if (contexts.Count == 0)
|
||||
{
|
||||
StringBuilder errors = new StringBuilder();
|
||||
if (!store.GetSupportedPaymentMethods(_NetworkProvider).Any())
|
||||
if (!store.GetPaymentMethodConfigs(_handlers).Any())
|
||||
errors.AppendLine(
|
||||
"Warning: No wallet has been linked to your BTCPay Store. See the following link for more information on how to connect your store and wallet. (https://docs.btcpayserver.org/WalletSetup/)");
|
||||
else
|
||||
|
@ -299,9 +262,13 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
throw new BitpayHttpException(400, errors.ToString());
|
||||
}
|
||||
entity.SetPaymentPrompts(new PaymentPromptDictionary(contexts.Select(c => c.Prompt)));
|
||||
}
|
||||
entity.SetSupportedPaymentMethods(supported);
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
else
|
||||
{
|
||||
entity.SetPaymentPrompts(new PaymentPromptDictionary());
|
||||
}
|
||||
|
||||
foreach (var app in await getAppsTaggingStore)
|
||||
{
|
||||
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
|
||||
|
@ -313,151 +280,18 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
using (logs.Measure("Saving invoice"))
|
||||
{
|
||||
await _InvoiceRepository.CreateInvoiceAsync(entity, additionalSearchTerms);
|
||||
var links = new List<WalletObjectLinkData>();
|
||||
foreach (var method in paymentMethods)
|
||||
{
|
||||
if (method.GetPaymentMethodDetails() is BitcoinLikeOnChainPaymentMethod bp)
|
||||
{
|
||||
var walletId = new WalletId(store.Id, method.GetId().CryptoCode);
|
||||
await _walletRepository.EnsureWalletObject(new WalletObjectId(
|
||||
walletId,
|
||||
WalletObjectData.Types.Invoice,
|
||||
entity.Id
|
||||
));
|
||||
if (bp.GetDepositAddress(((BTCPayNetwork)method.Network).NBitcoinNetwork) is BitcoinAddress address)
|
||||
{
|
||||
links.Add(WalletRepository.NewWalletObjectLinkData(new WalletObjectId(
|
||||
walletId,
|
||||
WalletObjectData.Types.Address,
|
||||
address.ToString()),
|
||||
new WalletObjectId(
|
||||
walletId,
|
||||
WalletObjectData.Types.Invoice,
|
||||
entity.Id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
await _walletRepository.EnsureCreated(null,links);
|
||||
await _InvoiceRepository.CreateInvoiceAsync(creationContext);
|
||||
await creationContext.ActivatingPaymentPrompt();
|
||||
}
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await fetchingAll;
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
ex.Handle(e => { logs.Write($"Error while fetching rates {ex}", InvoiceEventData.EventSeverity.Error); return true; });
|
||||
}
|
||||
await _InvoiceRepository.AddInvoiceLogs(entity.Id, logs);
|
||||
});
|
||||
_ = _InvoiceRepository.AddInvoiceLogs(entity.Id, logs);
|
||||
_EventAggregator.Publish(new Events.InvoiceEvent(entity, InvoiceEvent.Created));
|
||||
return entity;
|
||||
}
|
||||
|
||||
private Task WhenAllFetched(InvoiceLogs logs, Dictionary<CurrencyPair, Task<RateResult>> fetchingByCurrencyPair)
|
||||
private async Task FetchRates(InvoiceCreationContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.WhenAll(fetchingByCurrencyPair.Select(async pair =>
|
||||
{
|
||||
var rateResult = await pair.Value;
|
||||
logs.Write($"{pair.Key}: The rating rule is {rateResult.Rule}", InvoiceEventData.EventSeverity.Info);
|
||||
logs.Write($"{pair.Key}: The evaluated rating rule is {rateResult.EvaluatedRule}", InvoiceEventData.EventSeverity.Info);
|
||||
if (rateResult.Errors.Count != 0)
|
||||
{
|
||||
var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray());
|
||||
logs.Write($"{pair.Key}: Rate rule error ({allRateRuleErrors})", InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
foreach (var ex in rateResult.ExchangeExceptions)
|
||||
{
|
||||
logs.Write($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})", InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
}).ToArray());
|
||||
}
|
||||
|
||||
private async Task<PaymentMethod?> CreatePaymentMethodAsync(
|
||||
Dictionary<CurrencyPair, Task<RateResult>> fetchingByCurrencyPair,
|
||||
IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetworkBase network,
|
||||
InvoiceEntity entity,
|
||||
StoreData store, InvoiceLogs logs,
|
||||
HashSet<PaymentMethodId> invoicePaymentMethods)
|
||||
{
|
||||
try
|
||||
{
|
||||
var logPrefix = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
|
||||
// Checkout v2 does not show a payment method switch for Bitcoin-only + BIP21, so exclude that case
|
||||
var preparePayment = entity.LazyPaymentMethods && !storeBlob.OnChainWithLnInvoiceFallback
|
||||
? null
|
||||
: handler.PreparePayment(supportedPaymentMethod, store, network);
|
||||
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)];
|
||||
if (rate.BidAsk == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var paymentMethod = new PaymentMethod
|
||||
{
|
||||
ParentEntity = entity,
|
||||
Network = network,
|
||||
Rate = rate.BidAsk.Bid,
|
||||
PreferOnion = Uri.TryCreate(entity.ServerUrl, UriKind.Absolute, out var u) && u.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase)
|
||||
};
|
||||
paymentMethod.SetId(supportedPaymentMethod.PaymentId);
|
||||
|
||||
using (logs.Measure($"{logPrefix} Payment method details creation"))
|
||||
{
|
||||
var paymentDetails = await handler.CreatePaymentMethodDetails(logs, supportedPaymentMethod, paymentMethod, store, network, preparePayment, invoicePaymentMethods);
|
||||
paymentMethod.SetPaymentMethodDetails(paymentDetails);
|
||||
}
|
||||
|
||||
var criteria = storeBlob.PaymentMethodCriteria?.Find(methodCriteria => methodCriteria.PaymentMethod == supportedPaymentMethod.PaymentId);
|
||||
if (criteria?.Value != null && entity.Type != InvoiceType.TopUp)
|
||||
{
|
||||
var currentRateToCrypto =
|
||||
await fetchingByCurrencyPair[new CurrencyPair(supportedPaymentMethod.PaymentId.CryptoCode, criteria.Value.Currency)];
|
||||
if (currentRateToCrypto?.BidAsk != null)
|
||||
{
|
||||
var amount = paymentMethod.Calculate().Due;
|
||||
var limitValueCrypto = criteria.Value.Value / currentRateToCrypto.BidAsk.Bid;
|
||||
|
||||
if (amount < limitValueCrypto && criteria.Above)
|
||||
{
|
||||
logs.Write($"{logPrefix} invoice amount below accepted value for payment method", InvoiceEventData.EventSeverity.Error);
|
||||
return null;
|
||||
}
|
||||
if (amount > limitValueCrypto && !criteria.Above)
|
||||
{
|
||||
logs.Write($"{logPrefix} invoice amount above accepted value for payment method", InvoiceEventData.EventSeverity.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var suffix = currentRateToCrypto?.EvaluatedRule is string s ? $" ({s})" : string.Empty;
|
||||
logs.Write($"{logPrefix} This payment method should be created only if the amount of this invoice is in proper range. However, we are unable to fetch the rate of those limits. {suffix}", InvoiceEventData.EventSeverity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618
|
||||
if (paymentMethod.GetId().IsBTCOnChain)
|
||||
{
|
||||
entity.TxFee = paymentMethod.NextNetworkFee;
|
||||
entity.Rate = paymentMethod.Rate;
|
||||
entity.DepositAddress = paymentMethod.DepositAddress;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
return paymentMethod;
|
||||
}
|
||||
catch (PaymentMethodUnavailableException ex)
|
||||
{
|
||||
logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Payment method unavailable ({ex.Message})", InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex})", InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
return null;
|
||||
var rateRules = context.StoreBlob.GetRateRules(_NetworkProvider);
|
||||
await context.FetchingRates(_RateProvider, rateRules, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ using Microsoft.AspNetCore.Cors;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using LightningAddressData = BTCPayServer.Data.LightningAddressData;
|
||||
|
@ -47,8 +48,6 @@ namespace BTCPayServer
|
|||
{
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _appService;
|
||||
private readonly UIInvoiceController _invoiceController;
|
||||
|
@ -59,11 +58,11 @@ namespace BTCPayServer
|
|||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly IPluginHookService _pluginHookService;
|
||||
private readonly InvoiceActivator _invoiceActivator;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public UILNURLController(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
LightningLikePaymentHandler lightningLikePaymentHandler,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
StoreRepository storeRepository,
|
||||
AppService appService,
|
||||
UIInvoiceController invoiceController,
|
||||
|
@ -77,8 +76,7 @@ namespace BTCPayServer
|
|||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_lightningLikePaymentHandler = lightningLikePaymentHandler;
|
||||
_handlers = handlers;
|
||||
_storeRepository = storeRepository;
|
||||
_appService = appService;
|
||||
_invoiceController = invoiceController;
|
||||
|
@ -98,13 +96,13 @@ namespace BTCPayServer
|
|||
[NonAction]
|
||||
internal async Task<IActionResult> GetLNURLForPullPayment(string cryptoCode, string pullPaymentId, string pr, string k1, CancellationToken cancellationToken)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var network = GetNetwork(cryptoCode);
|
||||
if (network is null || !network.SupportLightning)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var pp = await _pullPaymentHostedService.GetPullPayment(pullPaymentId, true);
|
||||
if (!pp.IsRunning() || !pp.IsSupported(pmi))
|
||||
{
|
||||
|
@ -149,9 +147,7 @@ namespace BTCPayServer
|
|||
if (result.MinimumAmount < request.MinWithdrawable || result.MinimumAmount > request.MaxWithdrawable)
|
||||
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = $"Payment request was not within bounds ({request.MinWithdrawable.ToUnit(LightMoneyUnit.Satoshi)} - {request.MaxWithdrawable.ToUnit(LightMoneyUnit.Satoshi)} sats)" });
|
||||
var store = await _storeRepository.FindStore(pp.StoreId);
|
||||
var pm = store!.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(method => method.PaymentId == pmi);
|
||||
var pm = store!.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _handlers);
|
||||
if (pm is null)
|
||||
{
|
||||
return NotFound();
|
||||
|
@ -169,12 +165,13 @@ namespace BTCPayServer
|
|||
if (claimResponse.Result != ClaimRequest.ClaimResult.Ok)
|
||||
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Payment request could not be paid" });
|
||||
|
||||
var lightningHandler = _handlers.GetLightningHandler(network);
|
||||
switch (claimResponse.PayoutData.State)
|
||||
{
|
||||
case PayoutState.AwaitingPayment:
|
||||
{
|
||||
var client =
|
||||
_lightningLikePaymentHandler.CreateLightningClient(pm, network);
|
||||
lightningHandler.CreateLightningClient(pm);
|
||||
var payResult = await UILightningLikePayoutController.TrypayBolt(client,
|
||||
claimResponse.PayoutData.GetBlob(_btcPayNetworkJsonSerializerSettings),
|
||||
claimResponse.PayoutData, result, pmi, cancellationToken);
|
||||
|
@ -226,10 +223,18 @@ namespace BTCPayServer
|
|||
return Ok(request);
|
||||
}
|
||||
|
||||
private BTCPayNetwork GetNetwork(string cryptoCode)
|
||||
{
|
||||
if (!_handlers.TryGetValue(PaymentTypes.LNURL.GetPaymentMethodId(cryptoCode), out var o) ||
|
||||
o is not LNURLPayPaymentHandler { Network: var network })
|
||||
return null;
|
||||
return network;
|
||||
}
|
||||
|
||||
[HttpGet("pay/app/{appId}/{itemCode}")]
|
||||
public async Task<IActionResult> GetLNURLForApp(string cryptoCode, string appId, string itemCode = null)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var network = GetNetwork(cryptoCode);
|
||||
if (network is null || !network.SupportLightning)
|
||||
{
|
||||
return NotFound();
|
||||
|
@ -514,7 +519,7 @@ namespace BTCPayServer
|
|||
{
|
||||
createInvoice.Checkout ??= new InvoiceDataBase.CheckoutOptions();
|
||||
createInvoice.Checkout.LazyPaymentMethods = false;
|
||||
createInvoice.Checkout.PaymentMethods = new[] { pmi.ToStringNormalized() };
|
||||
createInvoice.Checkout.PaymentMethods = new[] { pmi.ToString() };
|
||||
i = await _invoiceController.CreateInvoiceCoreRaw(createInvoice, store, Request.GetAbsoluteRoot(), additionalTags);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -540,14 +545,17 @@ namespace BTCPayServer
|
|||
lnurlRequest ??= new LNURLPayRequest();
|
||||
lnUrlMetadata ??= new Dictionary<string, string>();
|
||||
|
||||
var pm = i.GetPaymentMethod(pmi);
|
||||
var pm = i.GetPaymentPrompt(pmi);
|
||||
if (pm is null)
|
||||
return null;
|
||||
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
|
||||
var handler = ((LNURLPayPaymentHandler)_handlers[pmi]);
|
||||
var paymentMethodDetails = handler.ParsePaymentPromptDetails(pm.Details);
|
||||
bool updatePaymentMethodDetails = false;
|
||||
List<string> searchTerms = new List<string>();
|
||||
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is not null)
|
||||
{
|
||||
paymentMethodDetails.ConsumedLightningAddress = lnAddress;
|
||||
searchTerms.Add(lnAddress);
|
||||
updatePaymentMethodDetails = true;
|
||||
}
|
||||
|
||||
|
@ -561,7 +569,7 @@ namespace BTCPayServer
|
|||
lnurlRequest.Callback = new Uri(_linkGenerator.GetUriByAction(
|
||||
action: nameof(GetLNURLForInvoice),
|
||||
controller: "UILNURL",
|
||||
values: new { pmi.CryptoCode, invoiceId = i.Id }, Request.Scheme, Request.Host, Request.PathBase));
|
||||
values: new { cryptoCode, invoiceId = i.Id }, Request.Scheme, Request.Host, Request.PathBase));
|
||||
lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
|
||||
if (i.Type != InvoiceType.TopUp)
|
||||
{
|
||||
|
@ -580,8 +588,10 @@ namespace BTCPayServer
|
|||
}
|
||||
if (updatePaymentMethodDetails)
|
||||
{
|
||||
pm.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(i.Id, pm);
|
||||
pm.Details = JToken.FromObject(paymentMethodDetails, handler.Serializer);
|
||||
await _invoiceRepository.UpdatePaymentDetails(i.Id, handler, paymentMethodDetails);
|
||||
await _invoiceRepository.AddSearchTerms(i.Id, searchTerms);
|
||||
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(i.Id, paymentMethodDetails, pmi));
|
||||
}
|
||||
return lnurlRequest;
|
||||
}
|
||||
|
@ -605,18 +615,16 @@ namespace BTCPayServer
|
|||
lnurlRequest.MaxSendable = LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC);
|
||||
}
|
||||
|
||||
PaymentMethodId GetLNUrlPaymentMethodId(string cryptoCode, Data.StoreData store, out LNURLPaySupportedPaymentMethod lnUrlSettings)
|
||||
PaymentMethodId GetLNUrlPaymentMethodId(string cryptoCode, Data.StoreData store, out LNURLPaymentMethodConfig lnUrlSettings)
|
||||
{
|
||||
lnUrlSettings = null;
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var network = GetNetwork(cryptoCode);
|
||||
if (network is null || !network.SupportLightning)
|
||||
return null;
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var lnpmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var methods = store.GetSupportedPaymentMethods(_btcPayNetworkProvider);
|
||||
var lnUrlMethod =
|
||||
methods.FirstOrDefault(method => method.PaymentId == pmi) as LNURLPaySupportedPaymentMethod;
|
||||
var lnMethod = methods.FirstOrDefault(method => method.PaymentId == lnpmi);
|
||||
var pmi = PaymentTypes.LNURL.GetPaymentMethodId(cryptoCode);
|
||||
var lnpmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var lnUrlMethod = store.GetPaymentMethodConfig<LNURLPaymentMethodConfig>(pmi, _handlers);
|
||||
var lnMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(lnpmi, _handlers);
|
||||
if (lnUrlMethod is null || lnMethod is null)
|
||||
return null;
|
||||
var blob = store.GetStoreBlob();
|
||||
|
@ -632,7 +640,7 @@ namespace BTCPayServer
|
|||
public async Task<IActionResult> GetLNURLForInvoice(string invoiceId, string cryptoCode,
|
||||
[FromQuery] long? amount = null, string comment = null)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var network = GetNetwork(cryptoCode);
|
||||
if (network is null || !network.SupportLightning)
|
||||
{
|
||||
return NotFound();
|
||||
|
@ -651,25 +659,25 @@ namespace BTCPayServer
|
|||
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out var lnurlSupportedPaymentMethod);
|
||||
if (pmi is null)
|
||||
return NotFound();
|
||||
|
||||
var lightningPaymentMethod = i.GetPaymentMethod(pmi);
|
||||
var paymentMethodDetails =
|
||||
lightningPaymentMethod?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
if (paymentMethodDetails is not null && !paymentMethodDetails.Activated)
|
||||
var handler = ((LNURLPayPaymentHandler)_handlers[pmi]);
|
||||
var lightningPaymentMethod = i.GetPaymentPrompt(pmi);
|
||||
var promptDetails = handler.ParsePaymentPromptDetails(lightningPaymentMethod.Details);
|
||||
if (promptDetails is null)
|
||||
{
|
||||
if (!await _invoiceActivator.ActivateInvoicePaymentMethod(pmi, i, store))
|
||||
if (!await _invoiceActivator.ActivateInvoicePaymentMethod(i.Id, pmi))
|
||||
return NotFound();
|
||||
i = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
lightningPaymentMethod = i.GetPaymentMethod(pmi);
|
||||
paymentMethodDetails = lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
lightningPaymentMethod = i.GetPaymentPrompt(pmi);
|
||||
promptDetails = handler.ParsePaymentPromptDetails(lightningPaymentMethod.Details);
|
||||
}
|
||||
|
||||
if (paymentMethodDetails?.LightningSupportedPaymentMethod is null)
|
||||
var lnConfig = _handlers.GetLightningConfig(store, network);
|
||||
if (lnConfig is null)
|
||||
return NotFound();
|
||||
|
||||
LNURLPayRequest lnurlPayRequest = paymentMethodDetails.PayRequest;
|
||||
LNURLPayRequest lnurlPayRequest = promptDetails.PayRequest;
|
||||
var blob = store.GetStoreBlob();
|
||||
if (paymentMethodDetails.PayRequest is null)
|
||||
if (promptDetails.PayRequest is null)
|
||||
{
|
||||
lnurlPayRequest = await CreateLNUrlRequestFromInvoice(cryptoCode, i, store, blob, allowOverpay: false);
|
||||
if (lnurlPayRequest is null)
|
||||
|
@ -705,23 +713,20 @@ namespace BTCPayServer
|
|||
if (lnurlSupportedPaymentMethod.LUD12Enabled)
|
||||
{
|
||||
comment = comment?.Truncate(2000);
|
||||
if (paymentMethodDetails.ProvidedComment != comment)
|
||||
if (promptDetails.ProvidedComment != comment)
|
||||
{
|
||||
paymentMethodDetails.ProvidedComment = comment;
|
||||
promptDetails.ProvidedComment = comment;
|
||||
updatePaymentMethod = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(paymentMethodDetails.BOLT11) || paymentMethodDetails.GeneratedBoltAmount != amt)
|
||||
if (string.IsNullOrEmpty(lightningPaymentMethod.Destination) || promptDetails.GeneratedBoltAmount != amt)
|
||||
{
|
||||
var client =
|
||||
_lightningLikePaymentHandler.CreateLightningClient(
|
||||
paymentMethodDetails.LightningSupportedPaymentMethod, network);
|
||||
if (!string.IsNullOrEmpty(paymentMethodDetails.BOLT11))
|
||||
var client = _handlers.GetLightningHandler(network).CreateLightningClient(lnConfig);
|
||||
if (!string.IsNullOrEmpty(lightningPaymentMethod.Destination))
|
||||
{
|
||||
try
|
||||
{
|
||||
await client.CancelInvoice(paymentMethodDetails.InvoiceId);
|
||||
await client.CancelInvoice(promptDetails.InvoiceId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -764,26 +769,26 @@ namespace BTCPayServer
|
|||
});
|
||||
}
|
||||
|
||||
paymentMethodDetails.BOLT11 = invoice.BOLT11;
|
||||
paymentMethodDetails.PaymentHash = string.IsNullOrEmpty(invoice.PaymentHash) ? null : uint256.Parse(invoice.PaymentHash);
|
||||
paymentMethodDetails.Preimage = string.IsNullOrEmpty(invoice.Preimage) ? null : uint256.Parse(invoice.Preimage);
|
||||
paymentMethodDetails.InvoiceId = invoice.Id;
|
||||
paymentMethodDetails.GeneratedBoltAmount = amt;
|
||||
lightningPaymentMethod.Destination = invoice.BOLT11;
|
||||
promptDetails.PaymentHash = string.IsNullOrEmpty(invoice.PaymentHash) ? null : uint256.Parse(invoice.PaymentHash);
|
||||
promptDetails.Preimage = string.IsNullOrEmpty(invoice.Preimage) ? null : uint256.Parse(invoice.Preimage);
|
||||
promptDetails.InvoiceId = invoice.Id;
|
||||
promptDetails.GeneratedBoltAmount = amt;
|
||||
lightningPaymentMethod.Details = JToken.FromObject(promptDetails, handler.Serializer);
|
||||
updatePaymentMethod = true;
|
||||
}
|
||||
|
||||
if (updatePaymentMethod)
|
||||
{
|
||||
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
|
||||
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId, paymentMethodDetails, pmi));
|
||||
await _invoiceRepository.UpdatePrompt(invoiceId, lightningPaymentMethod);
|
||||
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId, promptDetails, pmi));
|
||||
}
|
||||
|
||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||
{
|
||||
Disposable = true,
|
||||
Routes = Array.Empty<string>(),
|
||||
Pr = paymentMethodDetails.BOLT11,
|
||||
Pr = lightningPaymentMethod.Destination,
|
||||
SuccessAction = successAction
|
||||
});
|
||||
}
|
||||
|
@ -800,8 +805,8 @@ namespace BTCPayServer
|
|||
[HttpGet("~/stores/{storeId}/plugins/lightning-address")]
|
||||
public async Task<IActionResult> EditLightningAddress(string storeId)
|
||||
{
|
||||
if (ControllerContext.HttpContext.GetStoreData().GetEnabledPaymentIds(_btcPayNetworkProvider)
|
||||
.All(id => id.PaymentType != LNURLPayPaymentType.Instance))
|
||||
if (ControllerContext.HttpContext.GetStoreData().GetEnabledPaymentIds()
|
||||
.All(id => _handlers.TryGet(id) is not LNURLPayPaymentHandler))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
|
|
|
@ -121,7 +121,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
return NotFound();
|
||||
}
|
||||
if (!store.AnyPaymentMethodAvailable(_networkProvider))
|
||||
if (!store.AnyPaymentMethodAvailable())
|
||||
{
|
||||
return NoPaymentMethodResult(storeId);
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
return NotFound();
|
||||
}
|
||||
if (!store.AnyPaymentMethodAvailable(_networkProvider))
|
||||
if (!store.AnyPaymentMethodAvailable())
|
||||
{
|
||||
return NoPaymentMethodResult(store.Id);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -8,6 +9,7 @@ using BTCPayServer.Logging;
|
|||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -19,13 +21,19 @@ namespace BTCPayServer.Controllers
|
|||
public class UIPublicLightningNodeInfoController : Controller
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider;
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly LightningLikePaymentHandler _LightningLikePaymentHandler;
|
||||
private readonly StoreRepository _StoreRepository;
|
||||
|
||||
public UIPublicLightningNodeInfoController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
LightningLikePaymentHandler lightningLikePaymentHandler, StoreRepository storeRepository)
|
||||
{
|
||||
_BtcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_paymentModelExtensions = paymentModelExtensions;
|
||||
_handlers = handlers;
|
||||
_LightningLikePaymentHandler = lightningLikePaymentHandler;
|
||||
_StoreRepository = storeRepository;
|
||||
}
|
||||
|
@ -47,13 +55,13 @@ namespace BTCPayServer.Controllers
|
|||
};
|
||||
try
|
||||
{
|
||||
var paymentMethodDetails = GetExistingLightningSupportedPaymentMethod(cryptoCode, store);
|
||||
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var paymentMethodDetails = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _handlers);
|
||||
var network = _BtcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var nodeInfo = await _LightningLikePaymentHandler.GetNodeInfo(paymentMethodDetails, network,
|
||||
new InvoiceLogs(), throws: true);
|
||||
var nodeInfo = await _LightningLikePaymentHandler.GetNodeInfo(paymentMethodDetails, null, throws: true);
|
||||
|
||||
vm.Available = true;
|
||||
vm.CryptoImage = GetImage(paymentMethodDetails.PaymentId, network);
|
||||
vm.CryptoImage = GetImage(pmi);
|
||||
vm.NodeInfo = nodeInfo.Select(n => new ShowLightningNodeInfoViewModel.NodeData(n)).ToArray();
|
||||
}
|
||||
catch (Exception)
|
||||
|
@ -64,21 +72,13 @@ namespace BTCPayServer.Controllers
|
|||
return View(vm);
|
||||
}
|
||||
|
||||
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
private string GetImage(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_BtcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
|
||||
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
|
||||
{
|
||||
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
|
||||
? Url.Content(network.CryptoImagePath)
|
||||
: Url.Content(network.LightningImagePath);
|
||||
return "/" + res;
|
||||
if (_paymentModelExtensions.TryGetValue(paymentMethodId, out var paymentModelExtension))
|
||||
{
|
||||
return "/" + Url.Content(paymentModelExtension.Image);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ public partial class UIReportsController : Controller
|
|||
var vm = new StoreReportsViewModel
|
||||
{
|
||||
InvoiceTemplateUrl = Url.Action(nameof(UIInvoiceController.Invoice), "UIInvoice", new { invoiceId = "INVOICE_ID" }),
|
||||
ExplorerTemplateUrls = TransactionLinkProviders.ToDictionary(p => p.Key.CryptoCode, p => p.Value.BlockExplorerLink?.Replace("{0}", "TX_ID")),
|
||||
ExplorerTemplateUrls = TransactionLinkProviders.ToDictionary(p => p.Key, p => p.Value.BlockExplorerLink?.Replace("{0}", "TX_ID")),
|
||||
Request = new StoreReportRequest { ViewName = viewName ?? "Payments" },
|
||||
AvailableViews = ReportService.ReportProviders
|
||||
.Values
|
||||
|
|
|
@ -348,7 +348,7 @@ namespace BTCPayServer.Controllers
|
|||
return View(settings);
|
||||
}
|
||||
settings.BlockExplorerLinks = settings.BlockExplorerLinks
|
||||
.Where(tuple => _transactionLinkProviders.GetDefaultBlockExplorerLink(PaymentMethodId.Parse(tuple.CryptoCode)) != tuple.Link)
|
||||
.Where(tuple => _transactionLinkProviders.GetDefaultBlockExplorerLink(tuple.CryptoCode) != tuple.Link)
|
||||
.Where(tuple => tuple.Link is not null)
|
||||
.ToList();
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ namespace BTCPayServer.Controllers
|
|||
CustomCSSLink = "",
|
||||
EmbeddedCSS = "",
|
||||
PaymentMethodItems =
|
||||
paymentMethods.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true))
|
||||
paymentMethods.Select(id => new SelectListItem(id.ToString(), id.ToString(), true))
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
var paymentMethodOptions = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
|
||||
model.PaymentMethodItems =
|
||||
paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true));
|
||||
paymentMethodOptions.Select(id => new SelectListItem(id.ToString(), id.ToString(), true));
|
||||
model.Name ??= string.Empty;
|
||||
model.Currency = model.Currency?.ToUpperInvariant()?.Trim() ?? String.Empty;
|
||||
model.PaymentMethods ??= new List<string>();
|
||||
|
@ -115,7 +115,7 @@ namespace BTCPayServer.Controllers
|
|||
// Since we assign all payment methods to be selected by default above we need to update
|
||||
// them here to reflect user's selection so that they can correct their mistake
|
||||
model.PaymentMethodItems =
|
||||
paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), false));
|
||||
paymentMethodOptions.Select(id => new SelectListItem(id.ToString(), id.ToString(), false));
|
||||
ModelState.AddModelError(nameof(model.PaymentMethods), "You need at least one payment method");
|
||||
}
|
||||
if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null)
|
||||
|
@ -386,7 +386,7 @@ namespace BTCPayServer.Controllers
|
|||
case "pay":
|
||||
{
|
||||
if (handler is { })
|
||||
return await handler?.InitiatePayment(paymentMethodId, payoutIds);
|
||||
return await handler.InitiatePayment(paymentMethodId, payoutIds);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Paying via this payment method is not supported",
|
||||
|
|
|
@ -14,9 +14,12 @@ using BTCPayServer.Models;
|
|||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
|
@ -110,6 +113,9 @@ namespace BTCPayServer.Controllers
|
|||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var network = _ExplorerProvider.GetNetwork(vm.CryptoCode);
|
||||
var oldConf = _handlers.GetLightningConfig(store, network);
|
||||
|
||||
vm.CanUseInternalNode = CanUseInternalLightning(vm.CryptoCode);
|
||||
|
||||
if (vm.CryptoCode == null)
|
||||
|
@ -118,21 +124,13 @@ namespace BTCPayServer.Controllers
|
|||
return View(vm);
|
||||
}
|
||||
|
||||
var network = _ExplorerProvider.GetNetwork(vm.CryptoCode);
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||
|
||||
var paymentMethodId = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
|
||||
|
||||
LightningSupportedPaymentMethod? paymentMethod = null;
|
||||
LightningPaymentMethodConfig? paymentMethod = null;
|
||||
if (vm.LightningNodeType == LightningNodeType.Internal)
|
||||
{
|
||||
if (!CanUseInternalLightning(network.CryptoCode))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "You are not authorized to use the internal lightning node");
|
||||
return View(vm);
|
||||
}
|
||||
paymentMethod = new LightningSupportedPaymentMethod
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod = new LightningPaymentMethodConfig();
|
||||
paymentMethod.SetInternalNode();
|
||||
}
|
||||
else
|
||||
|
@ -142,47 +140,26 @@ namespace BTCPayServer.Controllers
|
|||
ModelState.AddModelError(nameof(vm.ConnectionString), "Please provide a connection string");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
ILightningClient? lightningClient = null;
|
||||
try
|
||||
{
|
||||
lightningClient = _lightningClientFactoryService.Create(vm.ConnectionString, network);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), $"Invalid URL ({e.Message})");
|
||||
return View(vm);
|
||||
}
|
||||
if (!User.IsInRole(Roles.ServerAdmin) && !lightningClient.IsSafe())
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "You are not a server admin, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'.");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
paymentMethod = new LightningSupportedPaymentMethod
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
paymentMethod.SetLightningUrl(lightningClient);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), ex.Message);
|
||||
return View(vm);
|
||||
}
|
||||
paymentMethod = new LightningPaymentMethodConfig();
|
||||
paymentMethod.ConnectionString = vm.ConnectionString;
|
||||
}
|
||||
|
||||
var handler = (LightningLikePaymentHandler)_handlers[paymentMethodId];
|
||||
var ctx = new PaymentMethodConfigValidationContext(_authorizationService, ModelState,
|
||||
JToken.FromObject(paymentMethod, handler.Serializer), User, oldConf is null ? null : JToken.FromObject(oldConf, handler.Serializer));
|
||||
await handler.ValidatePaymentMethodConfig(ctx);
|
||||
if (ctx.MissingPermission is not null)
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "You do not have the permissions to change this settings");
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "save":
|
||||
var lnurl = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
store.SetSupportedPaymentMethod(lnurl, new LNURLPaySupportedPaymentMethod()
|
||||
var lnurl = PaymentTypes.LNURL.GetPaymentMethodId(vm.CryptoCode);
|
||||
store.SetPaymentMethodConfig(_handlers[paymentMethodId], paymentMethod);
|
||||
store.SetPaymentMethodConfig(_handlers[lnurl], new LNURLPaymentMethodConfig()
|
||||
{
|
||||
CryptoCode = vm.CryptoCode,
|
||||
UseBech32Scheme = true,
|
||||
LUD12Enabled = false
|
||||
});
|
||||
|
@ -192,10 +169,9 @@ namespace BTCPayServer.Controllers
|
|||
return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode });
|
||||
|
||||
case "test":
|
||||
var handler = _ServiceProvider.GetRequiredService<LightningLikePaymentHandler>();
|
||||
try
|
||||
{
|
||||
var info = await handler.GetNodeInfo(paymentMethod, network, new InvoiceLogs(), Request.IsOnion(), true);
|
||||
var info = await handler.GetNodeInfo(paymentMethod, null, Request.IsOnion(), true);
|
||||
var hasPublicAddress = info.Any();
|
||||
if (!vm.SkipPortTest && hasPublicAddress)
|
||||
{
|
||||
|
@ -228,7 +204,8 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(cryptoCode, store);
|
||||
var lnId = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var lightning = GetConfig<LightningPaymentMethodConfig>(lnId, store);
|
||||
if (lightning == null)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "You need to connect to a Lightning node before adjusting its settings.";
|
||||
|
@ -240,7 +217,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
CryptoCode = cryptoCode,
|
||||
StoreId = storeId,
|
||||
Enabled = !excludeFilters.Match(lightning.PaymentId),
|
||||
Enabled = !excludeFilters.Match(lnId),
|
||||
LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate,
|
||||
LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi,
|
||||
LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints,
|
||||
|
@ -248,10 +225,11 @@ namespace BTCPayServer.Controllers
|
|||
};
|
||||
SetExistingValues(store, vm);
|
||||
|
||||
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
var lnurlId = PaymentTypes.LNURL.GetPaymentMethodId(vm.CryptoCode);
|
||||
var lnurl = GetConfig<LNURLPaymentMethodConfig>(lnurlId, store);
|
||||
if (lnurl != null)
|
||||
{
|
||||
vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurl.PaymentId);
|
||||
vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurlId);
|
||||
vm.LNURLBech32Mode = lnurl.UseBech32Scheme;
|
||||
vm.LUD12Enabled = lnurl.LUD12Enabled;
|
||||
}
|
||||
|
@ -280,10 +258,10 @@ namespace BTCPayServer.Controllers
|
|||
blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi;
|
||||
blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints;
|
||||
blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback;
|
||||
var lnurlId = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
|
||||
var lnurlId = PaymentTypes.LNURL.GetPaymentMethodId(vm.CryptoCode);
|
||||
blob.SetExcluded(lnurlId, !vm.LNURLEnabled);
|
||||
|
||||
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
var lnurl = GetConfig<LNURLPaymentMethodConfig>(PaymentTypes.LNURL.GetPaymentMethodId(vm.CryptoCode), store);
|
||||
if (lnurl is null || (
|
||||
lnurl.UseBech32Scheme != vm.LNURLBech32Mode ||
|
||||
lnurl.LUD12Enabled != vm.LUD12Enabled))
|
||||
|
@ -291,9 +269,8 @@ namespace BTCPayServer.Controllers
|
|||
needUpdate = true;
|
||||
}
|
||||
|
||||
store.SetSupportedPaymentMethod(new LNURLPaySupportedPaymentMethod
|
||||
store.SetPaymentMethodConfig(_handlers[lnurlId], new LNURLPaymentMethodConfig
|
||||
{
|
||||
CryptoCode = vm.CryptoCode,
|
||||
UseBech32Scheme = vm.LNURLBech32Mode,
|
||||
LUD12Enabled = vm.LUD12Enabled
|
||||
});
|
||||
|
@ -325,16 +302,16 @@ namespace BTCPayServer.Controllers
|
|||
return NotFound();
|
||||
|
||||
var network = _ExplorerProvider.GetNetwork(cryptoCode);
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(cryptoCode, store);
|
||||
var lightning = GetConfig<LightningPaymentMethodConfig>(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), store);
|
||||
if (lightning == null)
|
||||
return NotFound();
|
||||
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||
var paymentMethodId = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.SetExcluded(paymentMethodId, !enabled);
|
||||
if (!enabled)
|
||||
{
|
||||
storeBlob.SetExcluded(new PaymentMethodId(network.CryptoCode, PaymentTypes.LNURLPay), true);
|
||||
storeBlob.SetExcluded(PaymentTypes.LNURL.GetPaymentMethodId(network.CryptoCode), true);
|
||||
}
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _Repo.UpdateStore(store);
|
||||
|
@ -351,7 +328,7 @@ namespace BTCPayServer.Controllers
|
|||
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
||||
{
|
||||
vm.CanUseInternalNode = CanUseInternalLightning(vm.CryptoCode);
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
var lightning = GetConfig<LightningPaymentMethodConfig>(PaymentTypes.LN.GetPaymentMethodId(vm.CryptoCode), store);
|
||||
|
||||
if (lightning != null)
|
||||
{
|
||||
|
@ -364,22 +341,9 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
private LightningSupportedPaymentMethod? GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
private T? GetConfig<T>(PaymentMethodId paymentMethodId, StoreData store) where T: class
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
|
||||
private LNURLPaySupportedPaymentMethod? GetExistingLNURLSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<LNURLPaySupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
return store.GetPaymentMethodConfig<T>(paymentMethodId, _handlers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,10 @@ using BTCPayServer.Data;
|
|||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
|
@ -20,6 +23,7 @@ using NBitcoin.DataEncoders;
|
|||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
|
@ -83,7 +87,8 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
vm.Network = network;
|
||||
DerivationSchemeSettings strategy = null;
|
||||
|
||||
PaymentMethodId paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
BitcoinLikePaymentHandler handler = (BitcoinLikePaymentHandler)_handlers[paymentMethodId];
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
{
|
||||
|
@ -145,7 +150,11 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
else if (!string.IsNullOrEmpty(vm.Config))
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromJson(UnprotectString(vm.Config), network, out strategy))
|
||||
try
|
||||
{
|
||||
strategy = handler.ParsePaymentMethodConfig(JToken.Parse(UnprotectString(vm.Config)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Config), "Config file was not in the correct format");
|
||||
return View(vm.ViewName, vm);
|
||||
|
@ -158,17 +167,16 @@ namespace BTCPayServer.Controllers
|
|||
return View(vm.ViewName, vm);
|
||||
}
|
||||
|
||||
vm.Config = ProtectString(strategy.ToJson());
|
||||
vm.Config = ProtectString(JToken.FromObject(strategy, handler.Serializer).ToString());
|
||||
ModelState.Remove(nameof(vm.Config));
|
||||
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
if (vm.Confirmation)
|
||||
{
|
||||
try
|
||||
{
|
||||
await wallet.TrackAsync(strategy.AccountDerivation);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
|
||||
store.SetPaymentMethodConfig(_handlers[paymentMethodId], strategy);
|
||||
storeBlob.SetExcluded(paymentMethodId, false);
|
||||
storeBlob.PayJoinEnabled = strategy.IsHotWallet && !(vm.SetupRequest?.PayJoinEnabled is false);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
|
@ -186,7 +194,7 @@ namespace BTCPayServer.Controllers
|
|||
// This is success case when derivation scheme is added to the store
|
||||
return RedirectToAction(nameof(WalletSettings), new { storeId = vm.StoreId, cryptoCode = vm.CryptoCode });
|
||||
}
|
||||
return ConfirmAddresses(vm, strategy);
|
||||
return ConfirmAddresses(vm, strategy, network.NBXplorerNetwork);
|
||||
}
|
||||
|
||||
private string ProtectString(string str)
|
||||
|
@ -256,7 +264,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var handler = _handlers.GetBitcoinHandler(cryptoCode);
|
||||
var client = _ExplorerProvider.GetExplorerClient(cryptoCode);
|
||||
var isImport = method == WalletSetupMethod.Seed;
|
||||
var vm = new WalletSetupViewModel
|
||||
|
@ -322,7 +330,7 @@ namespace BTCPayServer.Controllers
|
|||
vm.RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString();
|
||||
vm.AccountKey = response.AccountHDKey.Neuter().ToWif();
|
||||
vm.KeyPath = response.AccountKeyPath.KeyPath.ToString();
|
||||
vm.Config = ProtectString(derivationSchemeSettings.ToJson());
|
||||
vm.Config = ProtectString(JToken.FromObject(derivationSchemeSettings, handler.Serializer).ToString());
|
||||
|
||||
var result = await UpdateWallet(vm);
|
||||
|
||||
|
@ -398,12 +406,13 @@ namespace BTCPayServer.Controllers
|
|||
(bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet();
|
||||
var client = _ExplorerProvider.GetExplorerClient(network);
|
||||
|
||||
var handler = _handlers.GetBitcoinHandler(cryptoCode);
|
||||
var vm = new WalletSettingsViewModel
|
||||
{
|
||||
StoreId = storeId,
|
||||
CryptoCode = cryptoCode,
|
||||
WalletId = new WalletId(storeId, cryptoCode),
|
||||
Enabled = !excludeFilters.Match(derivation.PaymentId),
|
||||
Enabled = !excludeFilters.Match(handler.PaymentMethodId),
|
||||
Network = network,
|
||||
IsHotWallet = derivation.IsHotWallet,
|
||||
Source = derivation.Source,
|
||||
|
@ -411,7 +420,7 @@ namespace BTCPayServer.Controllers
|
|||
DerivationScheme = derivation.AccountDerivation.ToString(),
|
||||
DerivationSchemeInput = derivation.AccountOriginal,
|
||||
KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString(),
|
||||
UriScheme = derivation.Network.NBitcoinNetwork.UriScheme,
|
||||
UriScheme = network.NBitcoinNetwork.UriScheme,
|
||||
Label = derivation.Label,
|
||||
SelectedSigningKey = derivation.SigningKey.ToString(),
|
||||
NBXSeedAvailable = derivation.IsHotWallet &&
|
||||
|
@ -425,7 +434,7 @@ namespace BTCPayServer.Controllers
|
|||
MasterFingerprint = e.RootFingerprint is HDFingerprint fp ? fp.ToString() : null,
|
||||
AccountKeyPath = e.AccountKeyPath == null ? "" : $"m/{e.AccountKeyPath}"
|
||||
}).ToList(),
|
||||
Config = ProtectString(derivation.ToJson()),
|
||||
Config = ProtectString(JToken.FromObject(derivation, handler.Serializer).ToString()),
|
||||
PayJoinEnabled = storeBlob.PayJoinEnabled,
|
||||
MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes,
|
||||
SpeedPolicy = store.SpeedPolicy,
|
||||
|
@ -433,10 +442,7 @@ namespace BTCPayServer.Controllers
|
|||
RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget,
|
||||
CanUseHotWallet = canUseHotWallet,
|
||||
CanUseRPCImport = rpcImport,
|
||||
CanUsePayJoin = canUseHotWallet && store
|
||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet),
|
||||
CanUsePayJoin = canUseHotWallet && network.SupportPayJoin && derivation.IsHotWallet,
|
||||
StoreName = store.StoreName,
|
||||
|
||||
};
|
||||
|
@ -451,7 +457,7 @@ namespace BTCPayServer.Controllers
|
|||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> UpdateWalletSettings(WalletSettingsViewModel vm)
|
||||
{
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out var store, out _);
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
|
@ -462,17 +468,17 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var handler = _handlers.GetBitcoinHandler(vm.CryptoCode);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||
var currentlyEnabled = !excludeFilters.Match(derivation.PaymentId);
|
||||
var currentlyEnabled = !excludeFilters.Match(handler.PaymentMethodId);
|
||||
bool enabledChanged = currentlyEnabled != vm.Enabled;
|
||||
bool needUpdate = enabledChanged;
|
||||
string errorMessage = null;
|
||||
|
||||
if (enabledChanged)
|
||||
{
|
||||
storeBlob.SetExcluded(derivation.PaymentId, !vm.Enabled);
|
||||
storeBlob.SetExcluded(handler.PaymentMethodId, !vm.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
}
|
||||
|
||||
|
@ -484,7 +490,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
var signingKey = string.IsNullOrEmpty(vm.SelectedSigningKey)
|
||||
? null
|
||||
: new BitcoinExtPubKey(vm.SelectedSigningKey, derivation.Network.NBitcoinNetwork);
|
||||
: new BitcoinExtPubKey(vm.SelectedSigningKey, network.NBitcoinNetwork);
|
||||
if (derivation.SigningKey != signingKey && signingKey != null)
|
||||
{
|
||||
needUpdate = true;
|
||||
|
@ -531,9 +537,15 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
if (store.SpeedPolicy != vm.SpeedPolicy)
|
||||
{
|
||||
store.SpeedPolicy = vm.SpeedPolicy;
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if (needUpdate)
|
||||
{
|
||||
store.SetSupportedPaymentMethod(derivation);
|
||||
store.SetPaymentMethodConfig(handler, derivation);
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
|
||||
|
@ -561,7 +573,7 @@ namespace BTCPayServer.Controllers
|
|||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> UpdatePaymentSettings(WalletSettingsViewModel vm)
|
||||
{
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out var store, out _);
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
|
@ -574,12 +586,6 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
bool needUpdate = false;
|
||||
if (store.SpeedPolicy != vm.SpeedPolicy)
|
||||
{
|
||||
needUpdate = true;
|
||||
store.SpeedPolicy = vm.SpeedPolicy;
|
||||
}
|
||||
|
||||
var blob = store.GetStoreBlob();
|
||||
var payjoinChanged = blob.PayJoinEnabled != vm.PayJoinEnabled;
|
||||
blob.MonitoringExpiration = TimeSpan.FromMinutes(vm.MonitoringExpiration);
|
||||
|
@ -598,21 +604,17 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
|
||||
|
||||
if (payjoinChanged && blob.PayJoinEnabled)
|
||||
{
|
||||
var problematicPayjoinEnabledMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Where(settings => settings.Network.SupportPayJoin && !settings.IsHotWallet)
|
||||
.Select(settings => settings.PaymentId.CryptoCode)
|
||||
.ToArray();
|
||||
|
||||
if (problematicPayjoinEnabledMethods.Any())
|
||||
if (payjoinChanged && blob.PayJoinEnabled && network.SupportPayJoin)
|
||||
{
|
||||
var config = store.GetPaymentMethodConfig<DerivationSchemeSettings>(PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode), _handlers);
|
||||
if (!config.IsHotWallet)
|
||||
{
|
||||
TempData.Remove(WellKnownTempData.SuccessMessage);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
||||
Html = $"The payment settings were updated successfully. However, PayJoin will not work for {string.Join(", ", problematicPayjoinEnabledMethods)} until you configure them to be a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
|
||||
Html = $"The payment settings were updated successfully. However, PayJoin will not work, as this isn't a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -743,8 +745,7 @@ namespace BTCPayServer.Controllers
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, null);
|
||||
store.SetPaymentMethodConfig(PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode), null);
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
_EventAggregator.Publish(new WalletChangedEvent { WalletId = new WalletId(storeId, cryptoCode) });
|
||||
|
@ -755,7 +756,7 @@ namespace BTCPayServer.Controllers
|
|||
return RedirectToAction(nameof(GeneralSettings), new { storeId });
|
||||
}
|
||||
|
||||
private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy)
|
||||
private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy, NBXplorerNetwork network)
|
||||
{
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
var deposit = new KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
|
@ -769,7 +770,7 @@ namespace BTCPayServer.Controllers
|
|||
var keyPath = deposit.GetKeyPath(i);
|
||||
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
|
||||
var derivation = line.Derive(i);
|
||||
var address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
|
||||
var address = network.CreateAddress(strategy.AccountDerivation,
|
||||
line.KeyPathTemplate.GetKeyPath(i),
|
||||
derivation.ScriptPubKey).ToString();
|
||||
vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath));
|
||||
|
@ -792,11 +793,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
return store.GetPaymentMethodConfig<DerivationSchemeSettings>(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), _handlers);
|
||||
}
|
||||
|
||||
private async Task<string> GetSeed(ExplorerClient client, DerivationSchemeSettings derivation)
|
||||
|
|
|
@ -18,6 +18,7 @@ using BTCPayServer.HostedServices.Webhooks;
|
|||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
|
@ -82,7 +83,7 @@ namespace BTCPayServer.Controllers
|
|||
_LangService = langService;
|
||||
_TokenController = tokenController;
|
||||
_WalletProvider = walletProvider;
|
||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
||||
_handlers = paymentMethodHandlerDictionary;
|
||||
_policiesSettings = policiesSettings;
|
||||
_authorizationService = authorizationService;
|
||||
_appService = appService;
|
||||
|
@ -118,7 +119,7 @@ namespace BTCPayServer.Controllers
|
|||
readonly SettingsRepository _settingsRepository;
|
||||
private readonly ExplorerClientProvider _ExplorerProvider;
|
||||
private readonly LanguageService _LangService;
|
||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly PoliciesSettings _policiesSettings;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly AppService _appService;
|
||||
|
@ -337,16 +338,15 @@ namespace BTCPayServer.Controllers
|
|||
var storeBlob = CurrentStore.GetStoreBlob();
|
||||
var vm = new CheckoutAppearanceViewModel();
|
||||
SetCryptoCurrencies(vm, CurrentStore);
|
||||
vm.PaymentMethodCriteria = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !storeBlob.GetExcludedPaymentMethods().Match(s.PaymentId))
|
||||
.Where(s => _NetworkProvider.GetNetwork(s.PaymentId.CryptoCode) != null)
|
||||
.Where(s => s.PaymentId.PaymentType != PaymentTypes.LNURLPay)
|
||||
.Select(method =>
|
||||
vm.PaymentMethodCriteria = CurrentStore.GetPaymentMethodConfigs(_handlers)
|
||||
.Where(s => !storeBlob.GetExcludedPaymentMethods().Match(s.Key) && s.Value is not LNURLPaymentMethodConfig)
|
||||
.Select(c =>
|
||||
{
|
||||
var pmi = c.Key;
|
||||
var existing = storeBlob.PaymentMethodCriteria.SingleOrDefault(criteria =>
|
||||
criteria.PaymentMethod == method.PaymentId);
|
||||
criteria.PaymentMethod == pmi);
|
||||
return existing is null
|
||||
? new PaymentMethodCriteriaViewModel { PaymentMethod = method.PaymentId.ToString(), Value = "" }
|
||||
? new PaymentMethodCriteriaViewModel { PaymentMethod = pmi.ToString(), Value = "" }
|
||||
: new PaymentMethodCriteriaViewModel
|
||||
{
|
||||
PaymentMethod = existing.PaymentMethod.ToString(),
|
||||
|
@ -391,13 +391,13 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
public PaymentMethodOptionViewModel.Format[] GetEnabledPaymentMethodChoices(StoreData storeData)
|
||||
{
|
||||
var enabled = storeData.GetEnabledPaymentIds(_NetworkProvider);
|
||||
var enabled = storeData.GetEnabledPaymentIds();
|
||||
|
||||
return enabled
|
||||
.Select(o =>
|
||||
new PaymentMethodOptionViewModel.Format()
|
||||
{
|
||||
Name = o.ToPrettyString(),
|
||||
Name = o.ToString(),
|
||||
Value = o.ToString(),
|
||||
PaymentId = o
|
||||
}).ToArray();
|
||||
|
@ -405,13 +405,13 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
PaymentMethodOptionViewModel.Format? GetDefaultPaymentMethodChoice(StoreData storeData)
|
||||
{
|
||||
var enabled = storeData.GetEnabledPaymentIds(_NetworkProvider);
|
||||
var enabled = storeData.GetEnabledPaymentIds();
|
||||
var defaultPaymentId = storeData.GetDefaultPaymentId();
|
||||
var defaultChoice = defaultPaymentId is not null ? defaultPaymentId.FindNearest(enabled) : null;
|
||||
if (defaultChoice is null)
|
||||
{
|
||||
defaultChoice = enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
|
||||
enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.LightningLike) ??
|
||||
defaultChoice = enabled.FirstOrDefault(e => e == PaymentTypes.CHAIN.GetPaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode)) ??
|
||||
enabled.FirstOrDefault(e => e == PaymentTypes.LN.GetPaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode)) ??
|
||||
enabled.FirstOrDefault();
|
||||
}
|
||||
var choices = GetEnabledPaymentMethodChoices(storeData);
|
||||
|
@ -507,15 +507,15 @@ namespace BTCPayServer.Controllers
|
|||
foreach (var newCriteria in model.PaymentMethodCriteria.ToList())
|
||||
{
|
||||
var paymentMethodId = PaymentMethodId.Parse(newCriteria.PaymentMethod);
|
||||
if (paymentMethodId.PaymentType == PaymentTypes.LightningLike)
|
||||
if (_handlers.TryGet(paymentMethodId) is LightningLikePaymentHandler h)
|
||||
model.PaymentMethodCriteria.Add(new PaymentMethodCriteriaViewModel()
|
||||
{
|
||||
PaymentMethod = new PaymentMethodId(paymentMethodId.CryptoCode, PaymentTypes.LNURLPay).ToString(),
|
||||
PaymentMethod = PaymentTypes.LNURL.GetPaymentMethodId(h.Network.CryptoCode).ToString(),
|
||||
Type = newCriteria.Type,
|
||||
Value = newCriteria.Value
|
||||
});
|
||||
// Should not be able to set LNUrlPay criteria directly in UI
|
||||
if (paymentMethodId.PaymentType == PaymentTypes.LNURLPay)
|
||||
if (_handlers.TryGet(paymentMethodId) is LNURLPayPaymentHandler)
|
||||
model.PaymentMethodCriteria.Remove(newCriteria);
|
||||
}
|
||||
blob.PaymentMethodCriteria ??= new List<PaymentMethodCriteria>();
|
||||
|
@ -575,54 +575,49 @@ namespace BTCPayServer.Controllers
|
|||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||
var derivationByCryptoCode =
|
||||
store
|
||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.ToDictionary(c => c.Network.CryptoCode.ToUpperInvariant());
|
||||
.GetPaymentMethodConfigs<DerivationSchemeSettings>(_handlers)
|
||||
.ToDictionary(c => ((IHasNetwork)_handlers[c.Key]).Network.CryptoCode, c => (DerivationSchemeSettings)c.Value);
|
||||
|
||||
var lightningByCryptoCode = store
|
||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.Where(method => method.PaymentId.PaymentType == LightningPaymentType.Instance)
|
||||
.ToDictionary(c => c.CryptoCode.ToUpperInvariant());
|
||||
.GetPaymentMethodConfigs(_handlers)
|
||||
.Where(c => c.Value is LightningPaymentMethodConfig)
|
||||
.ToDictionary(c => ((IHasNetwork)_handlers[c.Key]).Network.CryptoCode, c => (LightningPaymentMethodConfig)c.Value);
|
||||
|
||||
derivationSchemes = new List<StoreDerivationScheme>();
|
||||
lightningNodes = new List<StoreLightningNode>();
|
||||
|
||||
foreach (var paymentMethodId in _paymentMethodHandlerDictionary.Distinct().SelectMany(handler => handler.GetSupportedPaymentMethods()))
|
||||
foreach (var handler in _handlers)
|
||||
{
|
||||
switch (paymentMethodId.PaymentType)
|
||||
if (handler is BitcoinLikePaymentHandler { Network: var network })
|
||||
{
|
||||
case BitcoinPaymentType _:
|
||||
var strategy = derivationByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
var value = strategy?.ToPrettyString() ?? string.Empty;
|
||||
var strategy = derivationByCryptoCode.TryGet(network.CryptoCode);
|
||||
var value = strategy?.ToPrettyString() ?? string.Empty;
|
||||
|
||||
derivationSchemes.Add(new StoreDerivationScheme
|
||||
{
|
||||
Crypto = paymentMethodId.CryptoCode,
|
||||
WalletSupported = network.WalletSupported,
|
||||
Value = value,
|
||||
WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode),
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && strategy != null,
|
||||
derivationSchemes.Add(new StoreDerivationScheme
|
||||
{
|
||||
Crypto = network.CryptoCode,
|
||||
PaymentMethodId = handler.PaymentMethodId,
|
||||
WalletSupported = network.WalletSupported,
|
||||
Value = value,
|
||||
WalletId = new WalletId(store.Id, network.CryptoCode),
|
||||
Enabled = !excludeFilters.Match(handler.PaymentMethodId) && strategy != null,
|
||||
#if ALTCOINS
|
||||
Collapsed = network is Plugins.Altcoins.ElementsBTCPayNetwork elementsBTCPayNetwork && elementsBTCPayNetwork.NetworkCryptoCode != elementsBTCPayNetwork.CryptoCode && string.IsNullOrEmpty(value)
|
||||
Collapsed = network is Plugins.Altcoins.ElementsBTCPayNetwork elementsBTCPayNetwork && elementsBTCPayNetwork.NetworkCryptoCode != elementsBTCPayNetwork.CryptoCode && string.IsNullOrEmpty(value)
|
||||
#endif
|
||||
});
|
||||
break;
|
||||
|
||||
case LNURLPayPaymentType:
|
||||
break;
|
||||
|
||||
case LightningPaymentType _:
|
||||
var lightning = lightningByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
var isEnabled = !excludeFilters.Match(paymentMethodId) && lightning != null;
|
||||
lightningNodes.Add(new StoreLightningNode
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode,
|
||||
Address = lightning?.GetDisplayableConnectionString(),
|
||||
Enabled = isEnabled
|
||||
});
|
||||
break;
|
||||
});
|
||||
}
|
||||
else if (handler is LightningLikePaymentHandler)
|
||||
{
|
||||
var lnNetwork = ((IHasNetwork)handler).Network;
|
||||
var lightning = lightningByCryptoCode.TryGet(lnNetwork.CryptoCode);
|
||||
var isEnabled = !excludeFilters.Match(handler.PaymentMethodId) && lightning != null;
|
||||
lightningNodes.Add(new StoreLightningNode
|
||||
{
|
||||
CryptoCode = lnNetwork.CryptoCode,
|
||||
PaymentMethodId = handler.PaymentMethodId,
|
||||
Address = lightning?.GetDisplayableConnectionString(),
|
||||
Enabled = isEnabled
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -843,7 +838,7 @@ namespace BTCPayServer.Controllers
|
|||
var isOD = Regex.Match(derivationScheme, @"\(.*?\)");
|
||||
if (isOD.Success)
|
||||
{
|
||||
var derivationSchemeSettings = new DerivationSchemeSettings { Network = network };
|
||||
var derivationSchemeSettings = new DerivationSchemeSettings();
|
||||
var result = parser.ParseOutputDescriptor(derivationScheme);
|
||||
derivationSchemeSettings.AccountOriginal = derivationScheme.Trim();
|
||||
derivationSchemeSettings.AccountDerivation = result.Item1;
|
||||
|
@ -1087,8 +1082,8 @@ namespace BTCPayServer.Controllers
|
|||
if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
|
||||
{
|
||||
var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
StoreNotConfigured = !store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(p => !excludeFilter.Match(p.PaymentId))
|
||||
StoreNotConfigured = !store.GetPaymentMethodConfigs(_handlers)
|
||||
.Where(p => !excludeFilter.Match(p.Key))
|
||||
.Any();
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful";
|
||||
if (pairingResult == PairingResult.Partial)
|
||||
|
|
|
@ -9,6 +9,9 @@ using BTCPayServer.Client;
|
|||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Hwi;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -22,15 +25,15 @@ namespace BTCPayServer.Controllers
|
|||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
|
||||
public class UIVaultController : Controller
|
||||
{
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public UIVaultController(BTCPayNetworkProvider networks, IAuthorizationService authorizationService)
|
||||
public UIVaultController(PaymentMethodHandlerDictionary handlers, IAuthorizationService authorizationService)
|
||||
{
|
||||
Networks = networks;
|
||||
_handlers = handlers;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public BTCPayNetworkProvider Networks { get; }
|
||||
|
||||
[HttpGet]
|
||||
[Route("{cryptoCode}/xpub")]
|
||||
|
@ -46,8 +49,7 @@ namespace BTCPayServer.Controllers
|
|||
using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
|
||||
{
|
||||
var cancellationToken = cts.Token;
|
||||
var network = Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network == null)
|
||||
if (!_handlers.TryGetValue(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), out var h) || h is not IHasNetwork { Network: var network })
|
||||
return NotFound();
|
||||
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
var vaultClient = new VaultClient(websocket);
|
||||
|
@ -397,11 +399,8 @@ askdevice:
|
|||
|
||||
private DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId)
|
||||
{
|
||||
var paymentMethod = CurrentStore
|
||||
.GetSupportedPaymentMethods(Networks)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode);
|
||||
return paymentMethod;
|
||||
var pmi = Payments.PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||
return CurrentStore.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, _handlers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace BTCPayServer.Controllers
|
|||
// we just assume that it is 20 blocks
|
||||
var assumedFeeRate = await fr.GetFeeRateAsync(20);
|
||||
|
||||
var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(NetworkProvider, network.CryptoCode))?.AccountDerivation;
|
||||
var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_handlers, network.CryptoCode))?.AccountDerivation;
|
||||
if (derivationScheme is null)
|
||||
return NotFound();
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ using BTCPayServer.ModelBinders;
|
|||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
@ -69,6 +71,8 @@ namespace BTCPayServer.Controllers
|
|||
private readonly DelayedTransactionBroadcaster _broadcaster;
|
||||
private readonly PayjoinClient _payjoinClient;
|
||||
private readonly LabelService _labelService;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
private readonly WalletHistogramService _walletHistogramService;
|
||||
|
@ -94,10 +98,14 @@ namespace BTCPayServer.Controllers
|
|||
IServiceProvider serviceProvider,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
LabelService labelService,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||
TransactionLinkProviders transactionLinkProviders)
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
_labelService = labelService;
|
||||
_handlers = handlers;
|
||||
_paymentModelExtensions = paymentModelExtensions;
|
||||
_transactionLinkProviders = transactionLinkProviders;
|
||||
Repository = repo;
|
||||
WalletRepository = walletRepository;
|
||||
|
@ -176,11 +184,11 @@ namespace BTCPayServer.Controllers
|
|||
var stores = await Repository.GetStoresByUserId(GetUserId());
|
||||
|
||||
var onChainWallets = stores
|
||||
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Select(d => ((Wallet: _walletProvider.GetWallet(d.Network),
|
||||
DerivationStrategy: d.AccountDerivation,
|
||||
Network: d.Network)))
|
||||
.SelectMany(s => s.GetPaymentMethodConfigs<DerivationSchemeSettings>(_handlers)
|
||||
.Select(d => (
|
||||
Wallet: _walletProvider.GetWallet(((IHasNetwork)_handlers[d.Key]).Network),
|
||||
DerivationStrategy: d.Value.AccountDerivation,
|
||||
Network: ((IHasNetwork)_handlers[d.Key]).Network))
|
||||
.Where(_ => _.Wallet != null && _.Network.WalletSupported)
|
||||
.Select(_ => (Wallet: _.Wallet,
|
||||
Store: s,
|
||||
|
@ -225,8 +233,8 @@ namespace BTCPayServer.Controllers
|
|||
var paymentMethod = GetDerivationSchemeSettings(walletId);
|
||||
if (paymentMethod == null)
|
||||
return NotFound();
|
||||
|
||||
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
|
||||
var network = _handlers.GetBitcoinHandler(walletId.CryptoCode).Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
|
||||
// We can't filter at the database level if we need to apply label filter
|
||||
var preFiltering = string.IsNullOrEmpty(labelFilter);
|
||||
|
@ -253,12 +261,12 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
else
|
||||
{
|
||||
var pmi = new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike);
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||
foreach (var tx in transactions)
|
||||
{
|
||||
var vm = new ListTransactionsViewModel.TransactionViewModel();
|
||||
vm.Id = tx.TransactionId.ToString();
|
||||
vm.Link = _transactionLinkProviders.GetTransactionLink(pmi, vm.Id);
|
||||
vm.Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, vm.Id);
|
||||
vm.Timestamp = tx.SeenAt;
|
||||
vm.Positive = tx.BalanceChange.GetValue(wallet.Network) >= 0;
|
||||
vm.Balance = tx.BalanceChange.ShowMoney(wallet.Network);
|
||||
|
@ -325,7 +333,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey,
|
||||
Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint",
|
||||
new { walletId.CryptoCode })));
|
||||
new { cryptoCode = walletId.CryptoCode })));
|
||||
}
|
||||
|
||||
string[]? labels = null;
|
||||
|
@ -340,7 +348,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
CryptoCode = walletId.CryptoCode,
|
||||
Address = address?.ToString(),
|
||||
CryptoImage = GetImage(paymentMethod.PaymentId, network),
|
||||
CryptoImage = GetImage(network),
|
||||
PaymentLink = bip21.ToString(),
|
||||
ReturnUrl = returnUrl ?? HttpContext.Request.GetTypedHeaders().Referer?.AbsolutePath,
|
||||
SelectedLabels = labels ?? Array.Empty<string>()
|
||||
|
@ -449,7 +457,7 @@ namespace BTCPayServer.Controllers
|
|||
var storeData = store.GetStoreBlob();
|
||||
var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider);
|
||||
rateRules.Spread = 0.0m;
|
||||
var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, storeData.DefaultCurrency);
|
||||
var currencyPair = new Rating.CurrencyPair(walletId.CryptoCode, storeData.DefaultCurrency);
|
||||
double.TryParse(defaultAmount, out var amount);
|
||||
|
||||
var model = new WalletSendModel
|
||||
|
@ -591,7 +599,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
var utxos = await _walletProvider.GetWallet(network)
|
||||
.GetUnspentCoins(schemeSettings.AccountDerivation, false, cancellation);
|
||||
var pmi = new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike);
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(vm.CryptoCode);
|
||||
var walletTransactionsInfoAsync = await this.WalletRepository.GetWalletTransactionsInfo(walletId,
|
||||
utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
|
||||
vm.InputsAvailable = utxos.Select(coin =>
|
||||
|
@ -606,7 +614,7 @@ namespace BTCPayServer.Controllers
|
|||
Amount = coin.Value.GetValue(network),
|
||||
Comment = info?.Comment,
|
||||
Labels = _labelService.CreateTransactionTagModels(info, Request),
|
||||
Link = _transactionLinkProviders.GetTransactionLink(pmi, coin.OutPoint.ToString()),
|
||||
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, coin.OutPoint.ToString()),
|
||||
Confirmations = coin.Confirmations
|
||||
};
|
||||
}).ToArray();
|
||||
|
@ -753,7 +761,7 @@ namespace BTCPayServer.Controllers
|
|||
CreatePSBTResponse psbtResponse;
|
||||
if (command == "schedule")
|
||||
{
|
||||
var pmi = new PaymentMethodId(walletId.CryptoCode, BitcoinPaymentType.Instance);
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||
var claims =
|
||||
vm.Outputs.Where(output => string.IsNullOrEmpty(output.PayoutId)).Select(output => new ClaimRequest()
|
||||
{
|
||||
|
@ -1211,7 +1219,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
internal DerivationSchemeSettings? GetDerivationSchemeSettings(WalletId walletId)
|
||||
{
|
||||
return GetCurrentStore().GetDerivationSchemeSettings(NetworkProvider, walletId.CryptoCode);
|
||||
return GetCurrentStore().GetDerivationSchemeSettings(_handlers, walletId.CryptoCode);
|
||||
}
|
||||
|
||||
private static async Task<IMoney> GetBalanceAsMoney(BTCPayWallet wallet,
|
||||
|
@ -1251,7 +1259,8 @@ namespace BTCPayServer.Controllers
|
|||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||
if (derivationScheme == null || derivationScheme.Network.ReadonlyWallet)
|
||||
var network = _handlers.GetBitcoinHandler(walletId.CryptoCode).Network;
|
||||
if (derivationScheme == null || network.ReadonlyWallet)
|
||||
return NotFound();
|
||||
|
||||
switch (command)
|
||||
|
@ -1335,7 +1344,8 @@ namespace BTCPayServer.Controllers
|
|||
if (paymentMethod == null)
|
||||
return NotFound();
|
||||
|
||||
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
|
||||
var network = _handlers.GetBitcoinHandler(walletId.CryptoCode).Network;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId, (string[]?)null);
|
||||
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, cancellationToken: cancellationToken);
|
||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
|
@ -1469,12 +1479,14 @@ namespace BTCPayServer.Controllers
|
|||
return RedirectToAction(nameof(WalletLabels), new { walletId });
|
||||
}
|
||||
|
||||
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
|
||||
private string? GetImage(BTCPayNetwork network)
|
||||
{
|
||||
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
|
||||
? Url.Content(network.CryptoImagePath)
|
||||
: Url.Content(network.LightningImagePath);
|
||||
return Request.GetRelativePathOrAbsolute(res);
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
if (_paymentModelExtensions.TryGetValue(pmi, out var extension))
|
||||
{
|
||||
return Request.GetRelativePathOrAbsolute(Url.Content(extension.Image));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetUserId() => _userManager.GetUserId(User)!;
|
||||
|
|
|
@ -15,11 +15,6 @@ namespace BTCPayServer.Data
|
|||
return addressInvoiceData.Address;
|
||||
return addressInvoiceData.Address.Substring(0, index);
|
||||
}
|
||||
public static AddressInvoiceData Set(this AddressInvoiceData addressInvoiceData, string address, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
addressInvoiceData.Address = address + "#" + paymentMethodId.ToString();
|
||||
return addressInvoiceData;
|
||||
}
|
||||
public static PaymentMethodId GetPaymentMethodId(this AddressInvoiceData addressInvoiceData)
|
||||
{
|
||||
if (addressInvoiceData.Address == null)
|
||||
|
|
54
BTCPayServer/Data/BlobSerializer.cs
Normal file
54
BTCPayServer/Data/BlobSerializer.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
#nullable enable
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public static class BlobSerializer
|
||||
{
|
||||
public static (JsonSerializerSettings SerializerSettings, JsonSerializer Serializer) CreateSerializer()
|
||||
{
|
||||
JsonSerializerSettings settings = CreateSettings();
|
||||
return (settings, JsonSerializer.CreateDefault(settings));
|
||||
}
|
||||
public static (JsonSerializerSettings SerializerSettings, JsonSerializer Serializer) CreateSerializer(Network? network)
|
||||
{
|
||||
JsonSerializerSettings settings = CreateSettings();
|
||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(settings, network);
|
||||
settings.ContractResolver = CreateResolver();
|
||||
return (settings, JsonSerializer.CreateDefault(settings));
|
||||
}
|
||||
public static (JsonSerializerSettings SerializerSettings, JsonSerializer Serializer) CreateSerializer(NBXplorerNetwork network)
|
||||
{
|
||||
JsonSerializerSettings settings = CreateSettings();
|
||||
network.Serializer.ConfigureSerializer(settings);
|
||||
settings.ContractResolver = CreateResolver();
|
||||
return (settings, JsonSerializer.CreateDefault(settings));
|
||||
}
|
||||
|
||||
private static JsonSerializerSettings CreateSettings()
|
||||
{
|
||||
var settings = new JsonSerializerSettings()
|
||||
{
|
||||
ContractResolver = CreateResolver(),
|
||||
Formatting = Formatting.None,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||
};
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static CamelCasePropertyNamesContractResolver CreateResolver()
|
||||
{
|
||||
return new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
{
|
||||
NamingStrategy = new CamelCaseNamingStrategy
|
||||
{
|
||||
ProcessDictionaryKeys = false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,15 +12,17 @@ namespace BTCPayServer.Data
|
|||
{
|
||||
public static class IHasBlobExtensions
|
||||
{
|
||||
static readonly JsonSerializerSettings DefaultSerializer;
|
||||
static readonly JsonSerializerSettings DefaultSerializerSettings;
|
||||
static readonly JsonSerializer DefaultSerializer;
|
||||
static IHasBlobExtensions()
|
||||
{
|
||||
DefaultSerializer = new JsonSerializerSettings()
|
||||
DefaultSerializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
|
||||
Formatting = Formatting.None
|
||||
};
|
||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializer);
|
||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
||||
DefaultSerializer = JsonSerializer.CreateDefault(DefaultSerializerSettings);
|
||||
}
|
||||
class HasBlobWrapper<B> : IHasBlob<B>
|
||||
{
|
||||
|
@ -60,7 +62,7 @@ namespace BTCPayServer.Data
|
|||
public static B? GetBlob<B>(this IHasBlob<B> data, JsonSerializerSettings? settings = null)
|
||||
{
|
||||
if (data.Blob2 is not null)
|
||||
return JObject.Parse(data.Blob2).ToObject<B>(JsonSerializer.CreateDefault(settings ?? DefaultSerializer));
|
||||
return JObject.Parse(data.Blob2).ToObject<B>(JsonSerializer.CreateDefault(settings ?? DefaultSerializerSettings));
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (data.Blob is not null && data.Blob.Length != 0)
|
||||
{
|
||||
|
@ -69,7 +71,7 @@ namespace BTCPayServer.Data
|
|||
str = Encoding.UTF8.GetString(data.Blob);
|
||||
else
|
||||
str = ZipUtils.Unzip(data.Blob);
|
||||
return JObject.Parse(str).ToObject<B>(JsonSerializer.CreateDefault(settings ?? DefaultSerializer));
|
||||
return JObject.Parse(str).ToObject<B>(JsonSerializer.CreateDefault(settings ?? DefaultSerializerSettings));
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return default;
|
||||
|
@ -78,19 +80,28 @@ namespace BTCPayServer.Data
|
|||
public static object? GetBlob(this IHasBlob data, JsonSerializerSettings? settings = null)
|
||||
{
|
||||
if (data.Blob2 is not null)
|
||||
return JObject.Parse(data.Blob2).ToObject(data.Type, JsonSerializer.CreateDefault(settings ?? DefaultSerializer));
|
||||
return JObject.Parse(data.Blob2).ToObject(data.Type, JsonSerializer.CreateDefault(settings ?? DefaultSerializerSettings));
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (data.Blob is not null && data.Blob.Length != 0)
|
||||
return JObject.Parse(ZipUtils.Unzip(data.Blob)).ToObject(data.Type, JsonSerializer.CreateDefault(settings ?? DefaultSerializer));
|
||||
return JObject.Parse(ZipUtils.Unzip(data.Blob)).ToObject(data.Type, JsonSerializer.CreateDefault(settings ?? DefaultSerializerSettings));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return default;
|
||||
}
|
||||
public static T SetBlob<T, B>(this T data, B blob, JsonSerializerSettings? settings = null) where T : IHasBlob<B>
|
||||
|
||||
public static T SetBlob<T, B>(this T data, B blob) where T : IHasBlob<B>
|
||||
{
|
||||
return SetBlob(data, blob, (JsonSerializer?)null);
|
||||
}
|
||||
public static T SetBlob<T, B>(this T data, B blob, JsonSerializerSettings? settings) where T : IHasBlob<B>
|
||||
{
|
||||
return SetBlob(data, blob, settings is null ? null : JsonSerializer.CreateDefault(settings));
|
||||
}
|
||||
public static T SetBlob<T, B>(this T data, B blob, JsonSerializer? settings) where T : IHasBlob<B>
|
||||
{
|
||||
if (blob is null)
|
||||
data.Blob2 = null;
|
||||
else
|
||||
data.Blob2 = JObject.FromObject(blob, JsonSerializer.CreateDefault(settings ?? DefaultSerializer)).ToString(Formatting.None);
|
||||
data.Blob2 = JObject.FromObject(blob, settings ?? DefaultSerializer).ToString(Formatting.None);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
data.Blob = new byte[0];
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
|
|
@ -1,48 +1,69 @@
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public static class InvoiceDataExtensions
|
||||
{
|
||||
public static readonly JsonSerializerSettings DefaultSerializerSettings;
|
||||
public static readonly JsonSerializer DefaultSerializer;
|
||||
static InvoiceDataExtensions()
|
||||
{
|
||||
(DefaultSerializerSettings, DefaultSerializer) = BlobSerializer.CreateSerializer(null as NBitcoin.Network);
|
||||
}
|
||||
public static void SetBlob(this InvoiceData invoiceData, InvoiceEntity blob)
|
||||
{
|
||||
if (blob.Metadata is null)
|
||||
blob.Metadata = new InvoiceMetadata();
|
||||
invoiceData.HasTypedBlob<InvoiceEntity>().SetBlob(blob);
|
||||
invoiceData.Currency = blob.Currency;
|
||||
invoiceData.Amount = blob.Price;
|
||||
invoiceData.HasTypedBlob<InvoiceEntity>().SetBlob(blob, DefaultSerializer);
|
||||
}
|
||||
public static InvoiceEntity GetBlob(this InvoiceData invoiceData, BTCPayNetworkProvider networks)
|
||||
public static InvoiceEntity GetBlob(this InvoiceData invoiceData)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (invoiceData.Blob is not null && invoiceData.Blob.Length != 0)
|
||||
var entity = invoiceData.HasTypedBlob<InvoiceEntity>().GetBlob(DefaultSerializerSettings);
|
||||
entity.Payments = invoiceData.Payments?
|
||||
.Select(p => p.GetBlob())
|
||||
.OrderBy(a => a.ReceivedTime)
|
||||
.ToList();
|
||||
#pragma warning restore CS0618
|
||||
var state = invoiceData.GetInvoiceState();
|
||||
entity.Id = invoiceData.Id;
|
||||
entity.Currency = invoiceData.Currency;
|
||||
if (invoiceData.Amount is decimal price)
|
||||
{
|
||||
var entity = JsonConvert.DeserializeObject<InvoiceEntity>(ZipUtils.Unzip(invoiceData.Blob), InvoiceRepository.DefaultSerializerSettings);
|
||||
entity.Networks = networks;
|
||||
if (entity.Metadata is null)
|
||||
{
|
||||
if (entity.Version < InvoiceEntity.GreenfieldInvoices_Version)
|
||||
{
|
||||
entity.MigrateLegacyInvoice();
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.Metadata = new InvoiceMetadata();
|
||||
}
|
||||
}
|
||||
return entity;
|
||||
entity.Price = price;
|
||||
}
|
||||
else
|
||||
|
||||
entity.StoreId = invoiceData.StoreDataId;
|
||||
entity.ExceptionStatus = state.ExceptionStatus;
|
||||
entity.Status = state.Status;
|
||||
if (invoiceData.AddressInvoices != null)
|
||||
{
|
||||
var entity = invoiceData.HasTypedBlob<InvoiceEntity>().GetBlob();
|
||||
entity.Networks = networks;
|
||||
return entity;
|
||||
entity.AvailableAddressHashes = invoiceData.AddressInvoices.Select(a => a.GetAddress() + a.GetPaymentMethodId()).ToHashSet();
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
if (invoiceData.Events != null)
|
||||
{
|
||||
entity.Events = invoiceData.Events.OrderBy(c => c.Timestamp).ToList();
|
||||
}
|
||||
if (invoiceData.Refunds != null)
|
||||
{
|
||||
entity.Refunds = invoiceData.Refunds.OrderBy(c => c.PullPaymentData.StartDate).ToList();
|
||||
}
|
||||
entity.Archived = invoiceData.Archived;
|
||||
entity.UpdateTotals();
|
||||
return entity;
|
||||
}
|
||||
public static InvoiceState GetInvoiceState(this InvoiceData invoiceData)
|
||||
{
|
||||
return new InvoiceState(invoiceData.Status, invoiceData.ExceptionStatus);
|
||||
return new InvoiceState(invoiceData.Status ?? "new", invoiceData.ExceptionStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.InteropServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public static class PaymentDataExtensions
|
||||
{
|
||||
public static void SetBlob(this PaymentData paymentData, PaymentEntity entity)
|
||||
public static PaymentData Set(this PaymentData paymentData, InvoiceEntity invoiceEntity, IPaymentMethodHandler handler, object details)
|
||||
{
|
||||
paymentData.Type = entity.GetPaymentMethodId().ToStringNormalized();
|
||||
paymentData.Blob2 = entity.Network.ToString(entity);
|
||||
var prompt = invoiceEntity.GetPaymentPrompt(handler.PaymentMethodId) ?? throw new InvalidOperationException($"Payment prompt for {handler.PaymentMethodId} is not found");
|
||||
var paymentBlob = new PaymentBlob()
|
||||
{
|
||||
Destination = prompt.Destination,
|
||||
PaymentMethodFee = prompt.PaymentMethodFee,
|
||||
Divisibility = prompt.Divisibility
|
||||
}.SetDetails(handler, details);
|
||||
paymentData.InvoiceDataId = invoiceEntity.Id;
|
||||
paymentData.SetBlob(handler.PaymentMethodId, paymentBlob);
|
||||
return paymentData;
|
||||
}
|
||||
public static PaymentEntity GetBlob(this PaymentData paymentData, BTCPayNetworkProvider networks)
|
||||
public static PaymentEntity SetBlob(this PaymentData paymentData, PaymentEntity entity)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (paymentData.Blob is not null && paymentData.Blob.Length != 0)
|
||||
{
|
||||
var unziped = ZipUtils.Unzip(paymentData.Blob);
|
||||
var cryptoCode = "BTC";
|
||||
if (JObject.Parse(unziped).TryGetValue("cryptoCode", out var v) && v.Type == JTokenType.String)
|
||||
cryptoCode = v.Value<string>();
|
||||
var network = networks.GetNetwork<BTCPayNetworkBase>(cryptoCode);
|
||||
PaymentEntity paymentEntity = null;
|
||||
if (network == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
paymentEntity = network.ToObject<PaymentEntity>(unziped);
|
||||
}
|
||||
paymentEntity.Network = network;
|
||||
paymentEntity.Accounted = paymentData.Accounted;
|
||||
return paymentEntity;
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
if (paymentData.Blob2 is not null)
|
||||
{
|
||||
if (!PaymentMethodId.TryParse(paymentData.Type, out var pmi))
|
||||
return null;
|
||||
var network = networks.GetNetwork<BTCPayNetworkBase>(pmi.CryptoCode);
|
||||
if (network is null)
|
||||
return null;
|
||||
var entity = network.ToObject<PaymentEntity>(paymentData.Blob2);
|
||||
entity.Network = network;
|
||||
entity.Accounted = paymentData.Accounted;
|
||||
return entity;
|
||||
}
|
||||
return null;
|
||||
paymentData.Amount = entity.Value;
|
||||
paymentData.Currency = entity.Currency;
|
||||
paymentData.Status = entity.Status;
|
||||
paymentData.SetBlob(entity.PaymentMethodId, (PaymentBlob)entity);
|
||||
return entity;
|
||||
}
|
||||
public static PaymentData SetBlob(this PaymentData paymentData, PaymentMethodId paymentMethodId, PaymentBlob blob)
|
||||
{
|
||||
paymentData.Type = paymentMethodId.ToString();
|
||||
paymentData.Blob2 = JToken.FromObject(blob, InvoiceDataExtensions.DefaultSerializer).ToString(Newtonsoft.Json.Formatting.None);
|
||||
return paymentData;
|
||||
}
|
||||
|
||||
public static PaymentEntity GetBlob(this PaymentData paymentData)
|
||||
{
|
||||
var entity = JToken.Parse(paymentData.Blob2).ToObject<PaymentEntity>(InvoiceDataExtensions.DefaultSerializer) ?? throw new FormatException($"Invalid {nameof(PaymentEntity)}");
|
||||
entity.Status = paymentData.Status!.Value;
|
||||
entity.Currency = paymentData.Currency;
|
||||
entity.PaymentMethodId = PaymentMethodId.Parse(paymentData.Type);
|
||||
entity.Value = paymentData.Amount!.Value;
|
||||
entity.Id = paymentData.Id;
|
||||
entity.ReceivedTime = paymentData.Created!.Value;
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@ using BTCPayServer.Events;
|
|||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -32,6 +34,7 @@ using StoreData = BTCPayServer.Data.StoreData;
|
|||
public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
|
@ -43,6 +46,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
public WalletRepository WalletRepository { get; }
|
||||
|
||||
public BitcoinLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
WalletRepository walletRepository,
|
||||
ExplorerClientProvider explorerClientProvider,
|
||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||
|
@ -53,6 +57,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
TransactionLinkProviders transactionLinkProviders)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
WalletRepository = walletRepository;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
|
@ -66,20 +71,20 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
|
||||
public bool CanHandle(PaymentMethodId paymentMethod)
|
||||
{
|
||||
return paymentMethod?.PaymentType == BitcoinPaymentType.Instance &&
|
||||
_btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.CryptoCode)?.ReadonlyWallet is false;
|
||||
return _handlers.TryGetValue(paymentMethod, out var h) &&
|
||||
h is BitcoinLikePaymentHandler { Network: { ReadonlyWallet: false } };
|
||||
}
|
||||
|
||||
public async Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(claimRequest.PaymentMethodId.CryptoCode);
|
||||
var network = _handlers.GetNetwork(claimRequest.PaymentMethodId);
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(network);
|
||||
if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
||||
{
|
||||
|
||||
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
|
||||
await WalletRepository.AddWalletTransactionAttachment(
|
||||
new WalletId(claimRequest.StoreId, claimRequest.PaymentMethodId.CryptoCode),
|
||||
new WalletId(claimRequest.StoreId, network.CryptoCode),
|
||||
bitcoinLikeClaimDestination.Address.ToString(),
|
||||
Attachment.Payout(payoutData.PullPaymentDataId, payoutData.Id), WalletObjectData.Types.Address);
|
||||
}
|
||||
|
@ -87,7 +92,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
|
||||
public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
var network = _handlers.GetNetwork(paymentMethodId);
|
||||
destination = destination.Trim();
|
||||
try
|
||||
{
|
||||
|
@ -116,19 +121,19 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
return null;
|
||||
var paymentMethodId = payout.GetPaymentMethodId();
|
||||
if (paymentMethodId is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var cryptoCode = _handlers.TryGetNetwork(paymentMethodId)?.CryptoCode;
|
||||
if (cryptoCode is null)
|
||||
return null;
|
||||
ParseProofType(payout.Proof, out var raw, out var proofType);
|
||||
if (proofType == PayoutTransactionOnChainBlob.Type)
|
||||
{
|
||||
|
||||
var res = raw.ToObject<PayoutTransactionOnChainBlob>(
|
||||
JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(paymentMethodId.CryptoCode)));
|
||||
JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(cryptoCode)));
|
||||
if (res == null)
|
||||
return null;
|
||||
res.LinkTemplate = _transactionLinkProviders.GetBlockExplorerLink(paymentMethodId);
|
||||
res.LinkTemplate = _transactionLinkProviders.GetBlockExplorerLink(cryptoCode);
|
||||
return res;
|
||||
}
|
||||
return raw.ToObject<ManualPayoutProof>();
|
||||
|
@ -181,7 +186,8 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
|
||||
public Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
|
||||
{
|
||||
if (_btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode)?
|
||||
var network = _handlers.TryGetNetwork(paymentMethodId);
|
||||
if (network?
|
||||
.NBitcoinNetwork?
|
||||
.Consensus?
|
||||
.ConsensusFactory?
|
||||
|
@ -269,25 +275,23 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
|
||||
public Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
|
||||
{
|
||||
return Task.FromResult(storeData.GetEnabledPaymentIds(_btcPayNetworkProvider)
|
||||
.Where(id => id.PaymentType == BitcoinPaymentType.Instance));
|
||||
return Task.FromResult(storeData.GetPaymentMethodConfigs<DerivationSchemeSettings>(_handlers, true).Select(c => c.Key));
|
||||
}
|
||||
|
||||
public async Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)
|
||||
{
|
||||
await using var ctx = this._dbContextFactory.CreateContext();
|
||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var pmi = paymentMethodId.ToString();
|
||||
|
||||
var pmi = paymentMethodId;
|
||||
var payouts = await ctx.Payouts.Include(data => data.PullPaymentData)
|
||||
.Where(data => payoutIds.Contains(data.Id)
|
||||
&& pmi == data.PaymentMethodId
|
||||
&& pmi.ToString() == data.PaymentMethodId
|
||||
&& data.State == PayoutState.AwaitingPayment)
|
||||
.ToListAsync();
|
||||
|
||||
var pullPaymentIds = payouts.Select(data => data.PullPaymentDataId).Distinct().Where(s => s != null).ToArray();
|
||||
var storeId = payouts.First().StoreDataId;
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
var network = _handlers.GetNetwork(paymentMethodId);
|
||||
List<string> bip21 = new List<string>();
|
||||
foreach (var payout in payouts)
|
||||
{
|
||||
|
@ -315,10 +319,10 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
}
|
||||
}
|
||||
if (bip21.Any())
|
||||
return new RedirectToActionResult("WalletSend", "UIWallets", new { walletId = new WalletId(storeId, paymentMethodId.CryptoCode).ToString(), bip21 });
|
||||
return new RedirectToActionResult("WalletSend", "UIWallets", new { walletId = new WalletId(storeId, network.CryptoCode).ToString(), bip21 });
|
||||
return new RedirectToActionResult("Payouts", "UIWallets", new
|
||||
{
|
||||
walletId = new WalletId(storeId, paymentMethodId.CryptoCode).ToString(),
|
||||
walletId = new WalletId(storeId, network.CryptoCode).ToString(),
|
||||
pullPaymentId = pullPaymentIds.Length == 1 ? pullPaymentIds.First() : null
|
||||
});
|
||||
}
|
||||
|
@ -418,7 +422,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
var destinationSum =
|
||||
newTransaction.NewTransactionEvent.Outputs.Sum(output => output.Value.GetValue(network));
|
||||
var destination = addressTrackedSource.Address.ToString();
|
||||
var paymentMethodId = new PaymentMethodId(newTransaction.CryptoCode, BitcoinPaymentType.Instance);
|
||||
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(newTransaction.CryptoCode);
|
||||
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var payouts = await ctx.Payouts
|
||||
|
@ -443,7 +447,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
|||
return;
|
||||
|
||||
var derivationSchemeSettings = payout.StoreData
|
||||
.GetDerivationSchemeSettings(_btcPayNetworkProvider, newTransaction.CryptoCode)?.AccountDerivation;
|
||||
.GetDerivationSchemeSettings(_handlers, newTransaction.CryptoCode)?.AccountDerivation;
|
||||
if (derivationSchemeSettings is null)
|
||||
return;
|
||||
|
||||
|
|
|
@ -9,8 +9,10 @@ using BTCPayServer.Client.Models;
|
|||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -25,16 +27,15 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
|
||||
public const string LightningLikePayoutHandlerClearnetNamedClient =
|
||||
nameof(LightningLikePayoutHandlerClearnetNamedClient);
|
||||
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly UserService _userService;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public LightningLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
public LightningLikePayoutHandler(PaymentMethodHandlerDictionary handlers,
|
||||
IHttpClientFactory httpClientFactory, UserService userService, IAuthorizationService authorizationService)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_userService = userService;
|
||||
_authorizationService = authorizationService;
|
||||
|
@ -42,8 +43,8 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
|
||||
public bool CanHandle(PaymentMethodId paymentMethod)
|
||||
{
|
||||
return (paymentMethod.PaymentType == LightningPaymentType.Instance || paymentMethod.PaymentType == LNURLPayPaymentType.Instance) &&
|
||||
_btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.CryptoCode)?.SupportLightning is true;
|
||||
|
||||
return _handlers.TryGetValue(paymentMethod, out var h) && h is ILightningPaymentHandler;
|
||||
}
|
||||
|
||||
public Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData)
|
||||
|
@ -61,7 +62,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
public async Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken)
|
||||
{
|
||||
destination = destination.Trim();
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
var network = ((IHasNetwork)_handlers[paymentMethodId]).Network;
|
||||
try
|
||||
{
|
||||
string lnurlTag = null;
|
||||
|
@ -161,12 +162,12 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
public async Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
|
||||
{
|
||||
var result = new List<PaymentMethodId>();
|
||||
var methods = storeData.GetEnabledPaymentMethods(_btcPayNetworkProvider).Where(id => id.PaymentId.PaymentType == LightningPaymentType.Instance).OfType<LightningSupportedPaymentMethod>();
|
||||
foreach (LightningSupportedPaymentMethod supportedPaymentMethod in methods)
|
||||
var methods = storeData.GetPaymentMethodConfigs<LightningPaymentMethodConfig>(_handlers, true);
|
||||
foreach (var m in methods)
|
||||
{
|
||||
if (!supportedPaymentMethod.IsInternalNode)
|
||||
if (!m.Value.IsInternalNode)
|
||||
{
|
||||
result.Add(supportedPaymentMethod.PaymentId);
|
||||
result.Add(m.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -174,7 +175,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
{
|
||||
if (!await _userService.IsAdminUser(storeDataUserStore.ApplicationUserId))
|
||||
continue;
|
||||
result.Add(supportedPaymentMethod.PaymentId);
|
||||
result.Add(m.Key);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -185,8 +186,9 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
|
||||
public Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)
|
||||
{
|
||||
var cryptoCode = _handlers.GetNetwork(paymentMethodId).CryptoCode;
|
||||
return Task.FromResult<IActionResult>(new RedirectToActionResult("ConfirmLightningPayout",
|
||||
"UILightningLikePayout", new { cryptoCode = paymentMethodId.CryptoCode, payoutIds }));
|
||||
"UILightningLikePayout", new { cryptoCode, payoutIds }));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,9 +10,11 @@ using BTCPayServer.Configuration;
|
|||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -32,7 +34,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly LightningClientFactoryService _lightningClientFactoryService;
|
||||
private readonly IOptions<LightningNetworkOptions> _options;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
@ -43,7 +45,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
UserManager<ApplicationUser> userManager,
|
||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
StoreRepository storeRepository,
|
||||
LightningClientFactoryService lightningClientFactoryService,
|
||||
IOptions<LightningNetworkOptions> options,
|
||||
|
@ -54,7 +56,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
_userManager = userManager;
|
||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_handlers = handlers;
|
||||
_lightningClientFactoryService = lightningClientFactoryService;
|
||||
_options = options;
|
||||
_storeRepository = storeRepository;
|
||||
|
@ -101,7 +103,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
{
|
||||
await SetStoreContext();
|
||||
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
|
||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
var payouts = await GetPayouts(ctx, pmi, payoutIds);
|
||||
|
@ -125,14 +127,14 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
{
|
||||
await SetStoreContext();
|
||||
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||
var payoutHandler = (LightningLikePayoutHandler)_payoutHandlers.FindPayoutHandler(pmi);
|
||||
|
||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
|
||||
var payouts = (await GetPayouts(ctx, pmi, payoutIds)).GroupBy(data => data.StoreDataId);
|
||||
var results = new List<ResultVM>();
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(pmi.CryptoCode);
|
||||
var network = ((IHasNetwork)_handlers[pmi]).Network;
|
||||
|
||||
//we group per store and init the transfers by each
|
||||
|
||||
|
@ -141,9 +143,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
{
|
||||
var store = payoutDatas.First().StoreData;
|
||||
|
||||
var lightningSupportedPaymentMethod = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(method => method.PaymentId == pmi);
|
||||
var lightningSupportedPaymentMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _handlers);
|
||||
|
||||
if (lightningSupportedPaymentMethod.IsInternalNode && !authorizedForInternalNode)
|
||||
{
|
||||
|
|
|
@ -5,12 +5,15 @@ using System.Data;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
|
@ -24,21 +27,19 @@ namespace BTCPayServer.Data
|
|||
return defaultPaymentId;
|
||||
}
|
||||
|
||||
public static PaymentMethodId[] GetEnabledPaymentIds(this StoreData storeData, BTCPayNetworkProvider networks)
|
||||
public static PaymentMethodId[] GetEnabledPaymentIds(this StoreData storeData)
|
||||
{
|
||||
return GetEnabledPaymentMethods(storeData, networks).Select(method => method.PaymentId).ToArray();
|
||||
return storeData.GetPaymentMethodConfigs(true).Select(c => c.Key).ToArray();
|
||||
}
|
||||
|
||||
public static ISupportedPaymentMethod[] GetEnabledPaymentMethods(this StoreData storeData, BTCPayNetworkProvider networks)
|
||||
public static Dictionary<PaymentMethodId, object> GetEnabledPaymentMethods(this StoreData storeData, PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
var excludeFilter = storeData.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
var paymentMethodIds = storeData.GetSupportedPaymentMethods(networks)
|
||||
.Where(a => !excludeFilter.Match(a.PaymentId))
|
||||
.OrderByDescending(a => a.PaymentId.CryptoCode == "BTC")
|
||||
.ThenBy(a => a.PaymentId.CryptoCode)
|
||||
.ThenBy(a => a.PaymentId.PaymentType == PaymentTypes.LightningLike ? 1 : 0)
|
||||
.ToArray();
|
||||
return paymentMethodIds;
|
||||
return storeData.GetPaymentMethodConfigs(true)
|
||||
.Where(m => handlers.Support(m.Key))
|
||||
.OrderByDescending(a => a.Key.ToString() == "BTC")
|
||||
.ThenBy(a => a.Key.ToString())
|
||||
.ThenBy(a => handlers[a.Key].ParsePaymentMethodConfig(a.Value) is LightningPaymentMethodConfig ? 1 : 0)
|
||||
.ToDictionary(a => a.Key, a => handlers[a.Key].ParsePaymentMethodConfig(a.Value));
|
||||
}
|
||||
|
||||
public static void SetDefaultPaymentId(this StoreData storeData, PaymentMethodId? defaultPaymentId)
|
||||
|
@ -60,12 +61,9 @@ namespace BTCPayServer.Data
|
|||
return result;
|
||||
}
|
||||
|
||||
public static bool AnyPaymentMethodAvailable(this StoreData storeData, BTCPayNetworkProvider networkProvider)
|
||||
public static bool AnyPaymentMethodAvailable(this StoreData storeData)
|
||||
{
|
||||
var storeBlob = GetStoreBlob(storeData);
|
||||
var excludeFilter = storeBlob.GetExcludedPaymentMethods();
|
||||
|
||||
return GetSupportedPaymentMethods(storeData, networkProvider).Where(s => !excludeFilter.Match(s.PaymentId)).Any();
|
||||
return storeData.GetPaymentMethodConfigs(true).Any();
|
||||
}
|
||||
|
||||
public static bool SetStoreBlob(this StoreData storeData, StoreBlob storeBlob)
|
||||
|
@ -78,106 +76,94 @@ namespace BTCPayServer.Data
|
|||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<ISupportedPaymentMethod> GetSupportedPaymentMethods(this StoreData storeData, BTCPayNetworkProvider networks)
|
||||
public static object? GetPaymentMethodConfig(this StoreData storeData, Payments.PaymentMethodId paymentMethodId, PaymentMethodHandlerDictionary handlers, bool onlyEnabled = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(storeData);
|
||||
#pragma warning disable CS0618
|
||||
bool btcReturned = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(storeData.DerivationStrategies))
|
||||
var config = GetPaymentMethodConfig(storeData, paymentMethodId, onlyEnabled);
|
||||
if (config is null || !handlers.Support(paymentMethodId))
|
||||
return null;
|
||||
return handlers[paymentMethodId].ParsePaymentMethodConfig(config);
|
||||
}
|
||||
public static JToken? GetPaymentMethodConfig(this StoreData storeData, Payments.PaymentMethodId paymentMethodId, bool onlyEnabled = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(storeData.DerivationStrategies))
|
||||
return null;
|
||||
if (!onlyEnabled)
|
||||
{
|
||||
JObject strategies = JObject.Parse(storeData.DerivationStrategies);
|
||||
foreach (var strat in strategies.Properties())
|
||||
{
|
||||
if (!PaymentMethodId.TryParse(strat.Name, out var paymentMethodId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var network = networks.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
|
||||
if (network != null)
|
||||
{
|
||||
if (network == networks.BTC && paymentMethodId.PaymentType == PaymentTypes.BTCLike && btcReturned)
|
||||
continue;
|
||||
if (strat.Value.Type == JTokenType.Null)
|
||||
continue;
|
||||
yield return
|
||||
paymentMethodId.PaymentType.DeserializeSupportedPaymentMethod(network, strat.Value);
|
||||
}
|
||||
}
|
||||
return strategies[paymentMethodId.ToString()];
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
else
|
||||
{
|
||||
var excludeFilter = storeData.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
JObject strategies = JObject.Parse(storeData.DerivationStrategies);
|
||||
return excludeFilter.Match(paymentMethodId) ? null : strategies[paymentMethodId.ToString()];
|
||||
}
|
||||
}
|
||||
public static T? GetPaymentMethodConfig<T>(this StoreData storeData, Payments.PaymentMethodId paymentMethodId, PaymentMethodHandlerDictionary handlers, bool onlyEnabled = false) where T : class
|
||||
{
|
||||
var conf = storeData.GetPaymentMethodConfig(paymentMethodId, onlyEnabled);
|
||||
if (conf is null)
|
||||
return default;
|
||||
return handlers[paymentMethodId].ParsePaymentMethodConfig(conf) as T;
|
||||
}
|
||||
|
||||
public static void SetSupportedPaymentMethod(this StoreData storeData, ISupportedPaymentMethod supportedPaymentMethod)
|
||||
public static void SetPaymentMethodConfig(this StoreData storeData, IPaymentMethodHandler handler, object? config)
|
||||
{
|
||||
storeData.SetSupportedPaymentMethod(null, supportedPaymentMethod);
|
||||
storeData.SetPaymentMethodConfig(handler.PaymentMethodId, config is null ? null : JToken.FromObject(config, handler.Serializer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set or remove a new supported payment method for the store
|
||||
/// </summary>
|
||||
/// <param name="paymentMethodId">The paymentMethodId</param>
|
||||
/// <param name="supportedPaymentMethod">The payment method, or null to remove</param>
|
||||
public static void SetSupportedPaymentMethod(this StoreData storeData, PaymentMethodId? paymentMethodId, ISupportedPaymentMethod? supportedPaymentMethod)
|
||||
public static void SetPaymentMethodConfig(this StoreData storeData, PaymentMethodId paymentMethodId, JToken? config)
|
||||
{
|
||||
if (supportedPaymentMethod != null && paymentMethodId != null && paymentMethodId != supportedPaymentMethod.PaymentId)
|
||||
{
|
||||
throw new InvalidOperationException("Incoherent arguments, this should never happen");
|
||||
}
|
||||
if (supportedPaymentMethod == null && paymentMethodId == null)
|
||||
throw new ArgumentException($"{nameof(supportedPaymentMethod)} or {nameof(paymentMethodId)} should be specified");
|
||||
if (supportedPaymentMethod != null && paymentMethodId == null)
|
||||
{
|
||||
paymentMethodId = supportedPaymentMethod.PaymentId;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618
|
||||
JObject strategies = string.IsNullOrEmpty(storeData.DerivationStrategies) ? new JObject() : JObject.Parse(storeData.DerivationStrategies);
|
||||
bool existing = false;
|
||||
foreach (var strat in strategies.Properties().ToList())
|
||||
if (config is null)
|
||||
strategies.Remove(paymentMethodId.ToString());
|
||||
else
|
||||
strategies[paymentMethodId.ToString()] = config;
|
||||
storeData.DerivationStrategies = strategies.ToString(Newtonsoft.Json.Formatting.None);
|
||||
}
|
||||
public static Dictionary<PaymentMethodId, object> GetPaymentMethodConfigs(this StoreData storeData, PaymentMethodHandlerDictionary handlers, bool onlyEnabled = false)
|
||||
{
|
||||
return storeData.GetPaymentMethodConfigs(onlyEnabled)
|
||||
.Where(h => handlers.Support(h.Key))
|
||||
.ToDictionary(c => c.Key, c => handlers[c.Key].ParsePaymentMethodConfig(c.Value));
|
||||
}
|
||||
public static Dictionary<PaymentMethodId, T> GetPaymentMethodConfigs<T>(this StoreData storeData, PaymentMethodHandlerDictionary handlers, bool onlyEnabled = false) where T : class
|
||||
{
|
||||
return storeData.GetPaymentMethodConfigs(onlyEnabled)
|
||||
.Select(h => (h.Key, Config: handlers.TryGetValue(h.Key, out var handler) ? handler.ParsePaymentMethodConfig(h.Value) as T : null))
|
||||
.Where(h => h.Config is not null)
|
||||
.ToDictionary(c => c.Key, c => c.Config!);
|
||||
}
|
||||
public static Dictionary<PaymentMethodId, JToken> GetPaymentMethodConfigs(this StoreData storeData, bool onlyEnabled = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(storeData.DerivationStrategies))
|
||||
return new Dictionary<PaymentMethodId, JToken>();
|
||||
var excludeFilter = onlyEnabled ? storeData.GetStoreBlob().GetExcludedPaymentMethods() : null;
|
||||
var paymentMethodConfigurations = new Dictionary<PaymentMethodId, JToken>();
|
||||
JObject strategies = JObject.Parse(storeData.DerivationStrategies);
|
||||
foreach (var strat in strategies.Properties())
|
||||
{
|
||||
if (!PaymentMethodId.TryParse(strat.Name, out var stratId))
|
||||
{
|
||||
if (!PaymentMethodId.TryParse(strat.Name, out var paymentMethodId))
|
||||
continue;
|
||||
}
|
||||
if (stratId == paymentMethodId)
|
||||
{
|
||||
if (supportedPaymentMethod == null)
|
||||
{
|
||||
strat.Remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
strat.Value = PaymentMethodExtensions.Serialize(supportedPaymentMethod);
|
||||
}
|
||||
existing = true;
|
||||
break;
|
||||
}
|
||||
if (excludeFilter?.Match(paymentMethodId) is true)
|
||||
continue;
|
||||
paymentMethodConfigurations.Add(paymentMethodId, strat.Value);
|
||||
}
|
||||
if (!existing && supportedPaymentMethod != null)
|
||||
strategies.Add(new JProperty(supportedPaymentMethod.PaymentId.ToString(), PaymentMethodExtensions.Serialize(supportedPaymentMethod)));
|
||||
storeData.DerivationStrategies = strategies.ToString();
|
||||
#pragma warning restore CS0618
|
||||
return paymentMethodConfigurations;
|
||||
}
|
||||
|
||||
public static bool IsLightningEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode)
|
||||
public static bool IsLightningEnabled(this StoreData storeData, string cryptoCode)
|
||||
{
|
||||
return IsPaymentTypeEnabled(storeData, networks, cryptoCode, LightningPaymentType.Instance);
|
||||
return IsPaymentMethodEnabled(storeData, PaymentTypes.LN.GetPaymentMethodId(cryptoCode));
|
||||
}
|
||||
|
||||
public static bool IsLNUrlEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode)
|
||||
public static bool IsLNUrlEnabled(this StoreData storeData, string cryptoCode)
|
||||
{
|
||||
return IsPaymentTypeEnabled(storeData, networks, cryptoCode, LNURLPayPaymentType.Instance);
|
||||
return IsPaymentMethodEnabled(storeData, PaymentTypes.LNURL.GetPaymentMethodId(cryptoCode));
|
||||
}
|
||||
|
||||
private static bool IsPaymentTypeEnabled(this StoreData storeData, BTCPayNetworkProvider networks, string cryptoCode, PaymentType paymentType)
|
||||
private static bool IsPaymentMethodEnabled(this StoreData storeData, PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var paymentMethods = storeData.GetSupportedPaymentMethods(networks);
|
||||
var excludeFilters = storeData.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
return paymentMethods.Any(method =>
|
||||
method.PaymentId.CryptoCode == cryptoCode &&
|
||||
method.PaymentId.PaymentType == paymentType &&
|
||||
!excludeFilters.Match(method.PaymentId));
|
||||
return storeData.GetPaymentMethodConfig(paymentMethodId, true) is not null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class DerivationSchemeSettings : ISupportedPaymentMethod
|
||||
public class DerivationSchemeSettings
|
||||
{
|
||||
public static DerivationSchemeSettings Parse(string derivationStrategy, BTCPayNetwork network)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(network);
|
||||
ArgumentNullException.ThrowIfNull(derivationStrategy);
|
||||
var result = new DerivationSchemeSettings { Network = network };
|
||||
var result = new DerivationSchemeSettings();
|
||||
var parser = network.GetDerivationSchemeParser();
|
||||
if (parser.TryParseXpub(derivationStrategy, ref result) ||
|
||||
parser.TryParseXpub(derivationStrategy, ref result, electrum: true))
|
||||
|
@ -26,23 +26,9 @@ namespace BTCPayServer
|
|||
throw new FormatException($"Invalid Derivation Scheme");
|
||||
}
|
||||
|
||||
public static bool TryParseFromJson(string config, BTCPayNetwork network, out DerivationSchemeSettings strategy)
|
||||
public string GetNBXWalletId(Network network)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(network);
|
||||
ArgumentNullException.ThrowIfNull(config);
|
||||
strategy = null;
|
||||
try
|
||||
{
|
||||
strategy = network.NBXplorerNetwork.Serializer.ToObject<DerivationSchemeSettings>(config);
|
||||
strategy.Network = network;
|
||||
}
|
||||
catch { }
|
||||
return strategy != null;
|
||||
}
|
||||
|
||||
public string GetNBXWalletId()
|
||||
{
|
||||
return AccountDerivation is null ? null : DBUtils.nbxv1_get_wallet_id(Network.CryptoCode, AccountDerivation.ToString());
|
||||
return AccountDerivation is null ? null : DBUtils.nbxv1_get_wallet_id(network.NetworkSet.CryptoCode, AccountDerivation.ToString());
|
||||
}
|
||||
|
||||
public DerivationSchemeSettings()
|
||||
|
@ -55,7 +41,6 @@ namespace BTCPayServer
|
|||
ArgumentNullException.ThrowIfNull(network);
|
||||
ArgumentNullException.ThrowIfNull(derivationStrategy);
|
||||
AccountDerivation = derivationStrategy;
|
||||
Network = network;
|
||||
AccountKeySettings = derivationStrategy.GetExtPubKeys().Select(c => new AccountKeySettings()
|
||||
{
|
||||
AccountKey = c.GetWif(network.NBitcoinNetwork)
|
||||
|
@ -75,9 +60,6 @@ namespace BTCPayServer
|
|||
_SigningKey = value;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public BTCPayNetwork Network { get; set; }
|
||||
public string Source { get; set; }
|
||||
|
||||
public bool IsHotWallet { get; set; }
|
||||
|
@ -97,49 +79,15 @@ namespace BTCPayServer
|
|||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public BitcoinExtPubKey ExplicitAccountKey { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Obsolete("Use GetSigningAccountKeySettings().AccountKey instead")]
|
||||
public BitcoinExtPubKey AccountKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return ExplicitAccountKey ?? new BitcoinExtPubKey(AccountDerivation.GetExtPubKeys().First(), Network.NBitcoinNetwork);
|
||||
}
|
||||
}
|
||||
|
||||
public AccountKeySettings GetSigningAccountKeySettings()
|
||||
{
|
||||
return AccountKeySettings.Single(a => a.AccountKey == SigningKey);
|
||||
}
|
||||
|
||||
AccountKeySettings[] _AccountKeySettings;
|
||||
public AccountKeySettings[] AccountKeySettings
|
||||
{
|
||||
get
|
||||
{
|
||||
// Legacy
|
||||
if (_AccountKeySettings == null)
|
||||
{
|
||||
if (this.Network == null)
|
||||
return null;
|
||||
_AccountKeySettings = AccountDerivation.GetExtPubKeys().Select(e => new AccountKeySettings()
|
||||
{
|
||||
AccountKey = e.GetWif(this.Network.NBitcoinNetwork),
|
||||
}).ToArray();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
_AccountKeySettings[0].AccountKeyPath = AccountKeyPath;
|
||||
_AccountKeySettings[0].RootFingerprint = RootFingerprint;
|
||||
ExplicitAccountKey = null;
|
||||
AccountKeyPath = null;
|
||||
RootFingerprint = null;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
return _AccountKeySettings;
|
||||
}
|
||||
set
|
||||
{
|
||||
_AccountKeySettings = value;
|
||||
}
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public IEnumerable<NBXplorer.Models.PSBTRebaseKeyRules> GetPSBTRebaseKeyRules()
|
||||
|
@ -159,9 +107,6 @@ namespace BTCPayServer
|
|||
|
||||
public string Label { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public PaymentMethodId PaymentId => new PaymentMethodId(Network.CryptoCode, PaymentTypes.BTCLike);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return AccountDerivation.ToString();
|
||||
|
@ -173,11 +118,6 @@ namespace BTCPayServer
|
|||
ToString();
|
||||
}
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
return Network.NBXplorerNetwork.Serializer.ToString(this);
|
||||
}
|
||||
|
||||
public void RebaseKeyPaths(PSBT psbt)
|
||||
{
|
||||
foreach (var rebase in GetPSBTRebaseKeyRules())
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue