mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 22:11:48 +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;
|
||||||
using System.Data.Common;
|
|
||||||
using BTCPayServer.Abstractions.Models;
|
using BTCPayServer.Abstractions.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
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;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Client
|
namespace BTCPayServer.Client
|
||||||
{
|
{
|
||||||
public partial class BTCPayServerClient
|
public partial class BTCPayServerClient
|
||||||
{
|
{
|
||||||
public virtual async Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
|
public virtual async Task<OnChainPaymentMethodPreviewResultData>
|
||||||
bool? enabled = null,
|
PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||||
CancellationToken token = default)
|
string storeId, string paymentMethodId, string derivationScheme, int offset = 0,
|
||||||
{
|
int amount = 10,
|
||||||
var query = new Dictionary<string, object>();
|
CancellationToken token = default)
|
||||||
if (enabled != null)
|
{
|
||||||
{
|
var response = await _httpClient.SendAsync(
|
||||||
query.Add(nameof(enabled), enabled);
|
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 =
|
public virtual async Task<OnChainPaymentMethodPreviewResultData> PreviewStoreOnChainPaymentMethodAddresses(
|
||||||
await _httpClient.SendAsync(
|
string storeId, string paymentMethodId, int offset = 0, int amount = 10,
|
||||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain",
|
CancellationToken token = default)
|
||||||
query), token);
|
{
|
||||||
return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response);
|
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,
|
public virtual async Task<GenerateOnChainWalletResponse> GenerateOnChainWallet(string storeId,
|
||||||
string cryptoCode, CancellationToken token = default)
|
string paymentMethodId, GenerateOnChainWalletRequest request,
|
||||||
{
|
CancellationToken token = default)
|
||||||
var response =
|
{
|
||||||
await _httpClient.SendAsync(
|
var response = await _httpClient.SendAsync(
|
||||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}"), token);
|
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/generate",
|
||||||
return await HandleResponse<OnChainPaymentMethodData>(response);
|
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.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
|
@ -7,21 +8,60 @@ namespace BTCPayServer.Client
|
||||||
{
|
{
|
||||||
public partial class BTCPayServerClient
|
public partial class BTCPayServerClient
|
||||||
{
|
{
|
||||||
public virtual async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId,
|
public virtual async Task<GenericPaymentMethodData> UpdateStorePaymentMethod(
|
||||||
bool? enabled = null,
|
string storeId,
|
||||||
|
string paymentMethodId,
|
||||||
|
UpdatePaymentMethodRequest request,
|
||||||
CancellationToken token = default)
|
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>();
|
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 =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
await _httpClient.SendAsync(
|
||||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods",
|
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods",
|
||||||
query), token);
|
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 System.Collections.Generic;
|
||||||
|
using BTCPayServer.Client.JsonConverters;
|
||||||
using BTCPayServer.JsonConverters;
|
using BTCPayServer.JsonConverters;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.JsonConverters;
|
using NBitcoin.JsonConverters;
|
||||||
|
@ -21,7 +22,7 @@ namespace BTCPayServer.Client.Models
|
||||||
public bool ProceedWithPayjoin { get; set; } = true;
|
public bool ProceedWithPayjoin { get; set; } = true;
|
||||||
public bool ProceedWithBroadcast { get; set; } = true;
|
public bool ProceedWithBroadcast { get; set; } = true;
|
||||||
public bool NoChange { get; set; } = false;
|
public bool NoChange { get; set; } = false;
|
||||||
[JsonProperty(ItemConverterType = typeof(OutpointJsonConverter))]
|
[JsonProperty(ItemConverterType = typeof(SaneOutpointJsonConverter))]
|
||||||
public List<OutPoint> SelectedInputs { get; set; } = null;
|
public List<OutPoint> SelectedInputs { get; set; } = null;
|
||||||
public List<CreateOnChainTransactionRequestDestination> Destinations { get; set; }
|
public List<CreateOnChainTransactionRequestDestination> Destinations { get; set; }
|
||||||
[JsonProperty("rbf")]
|
[JsonProperty("rbf")]
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using BTCPayServer.Client.JsonConverters;
|
using BTCPayServer.Client.JsonConverters;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Client
|
namespace BTCPayServer.Client
|
||||||
{
|
{
|
||||||
|
@ -22,4 +25,16 @@ namespace BTCPayServer.Client
|
||||||
public bool ImportKeysToRPC { get; set; }
|
public bool ImportKeysToRPC { get; set; }
|
||||||
public bool SavePrivateKeys { 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
|
namespace BTCPayServer.Client.Models
|
||||||
{
|
{
|
||||||
public class GenericPaymentMethodData
|
public class GenericPaymentMethodData
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public object Data { get; set; }
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
public string CryptoCode { get; set; }
|
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; }
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||||
public decimal NetworkFee { get; set; }
|
public decimal PaymentMethodFee { get; set; }
|
||||||
|
|
||||||
public List<Payment> Payments { get; set; }
|
public List<Payment> Payments { get; set; }
|
||||||
public string PaymentMethod { get; set; }
|
public string PaymentMethodId { get; set; }
|
||||||
|
public JToken AdditionalData { get; set; }
|
||||||
public string CryptoCode { get; set; }
|
public string Currency { get; set; }
|
||||||
public JObject AdditionalData { get; set; }
|
|
||||||
|
|
||||||
public class Payment
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using BTCPayServer.Client.JsonConverters;
|
||||||
using BTCPayServer.JsonConverters;
|
using BTCPayServer.JsonConverters;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.JsonConverters;
|
using NBitcoin.JsonConverters;
|
||||||
|
@ -12,7 +13,7 @@ namespace BTCPayServer.Client.Models
|
||||||
public string Comment { get; set; }
|
public string Comment { get; set; }
|
||||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
[JsonConverter(typeof(OutpointJsonConverter))]
|
[JsonConverter(typeof(SaneOutpointJsonConverter))]
|
||||||
public OutPoint Outpoint { get; set; }
|
public OutPoint Outpoint { get; set; }
|
||||||
public string Link { get; set; }
|
public string Link { get; set; }
|
||||||
#pragma warning disable CS0612 // Type or member is obsolete
|
#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 bool AfterExpiration { get; set; }
|
||||||
public string PaymentMethod { get; set; }
|
public string PaymentMethodId { get; set; }
|
||||||
public InvoicePaymentMethodDataModel.Payment Payment { get; set; }
|
public InvoicePaymentMethodDataModel.Payment Payment { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,11 @@ namespace BTCPayServer
|
||||||
{
|
{
|
||||||
return transactionInformationSet;
|
return transactionInformationSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetTrackedDestination(Script scriptPubKey)
|
||||||
|
{
|
||||||
|
return scriptPubKey.Hash.ToString() + "#" + CryptoCode.ToUpperInvariant();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class BTCPayNetworkBase
|
public abstract class BTCPayNetworkBase
|
||||||
|
|
|
@ -17,6 +17,7 @@ namespace BTCPayServer.Data
|
||||||
public override ApplicationDbContext CreateContext()
|
public override ApplicationDbContext CreateContext()
|
||||||
{
|
{
|
||||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||||
|
builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance);
|
||||||
ConfigureBuilder(builder);
|
ConfigureBuilder(builder);
|
||||||
return new ApplicationDbContext(builder.Options);
|
return new ApplicationDbContext(builder.Options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.1" />
|
||||||
|
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.23" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||||
|
|
|
@ -6,11 +6,6 @@ namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public class AddressInvoiceData
|
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 string Address { get; set; }
|
||||||
public InvoiceData InvoiceData { get; set; }
|
public InvoiceData InvoiceData { get; set; }
|
||||||
public string InvoiceDataId { 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public class InvoiceData : IHasBlobUntyped
|
public partial class InvoiceData : IHasBlobUntyped
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
public string Currency { get; set; }
|
||||||
|
public decimal? Amount { get; set; }
|
||||||
public string StoreDataId { get; set; }
|
public string StoreDataId { get; set; }
|
||||||
public StoreData StoreData { get; set; }
|
public StoreData StoreData { get; set; }
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ namespace BTCPayServer.Data
|
||||||
public string OrderId { get; set; }
|
public string OrderId { get; set; }
|
||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public string ExceptionStatus { get; set; }
|
public string ExceptionStatus { get; set; }
|
||||||
|
[Obsolete("Unused")]
|
||||||
public string CustomerEmail { get; set; }
|
public string CustomerEmail { get; set; }
|
||||||
public List<AddressInvoiceData> AddressInvoices { get; set; }
|
public List<AddressInvoiceData> AddressInvoices { get; set; }
|
||||||
public bool Archived { 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.StoreDataId);
|
||||||
builder.Entity<InvoiceData>().HasIndex(o => o.OrderId);
|
builder.Entity<InvoiceData>().HasIndex(o => o.OrderId);
|
||||||
builder.Entity<InvoiceData>().HasIndex(o => o.Created);
|
builder.Entity<InvoiceData>().HasIndex(o => o.Created);
|
||||||
|
|
||||||
if (databaseFacade.IsNpgsql())
|
if (databaseFacade.IsNpgsql())
|
||||||
{
|
{
|
||||||
builder.Entity<InvoiceData>()
|
builder.Entity<InvoiceData>()
|
||||||
.Property(o => o.Blob2)
|
.Property(o => o.Blob2)
|
||||||
.HasColumnType("JSONB");
|
.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
|
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 Id { get; set; }
|
||||||
public string InvoiceDataId { get; set; }
|
public string InvoiceDataId { get; set; }
|
||||||
|
public string Currency { get; set; }
|
||||||
|
public decimal? Amount { get; set; }
|
||||||
public InvoiceData InvoiceData { get; set; }
|
public InvoiceData InvoiceData { get; set; }
|
||||||
[Obsolete("Use Blob2 instead")]
|
[Obsolete("Use Blob2 instead")]
|
||||||
public byte[] Blob { get; set; }
|
public byte[] Blob { get; set; }
|
||||||
public string Blob2 { get; set; }
|
public string Blob2 { get; set; }
|
||||||
public string Type { 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)
|
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||||
{
|
{
|
||||||
|
@ -23,11 +38,17 @@ namespace BTCPayServer.Data
|
||||||
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
|
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
|
||||||
builder.Entity<PaymentData>()
|
builder.Entity<PaymentData>()
|
||||||
.HasIndex(o => o.InvoiceDataId);
|
.HasIndex(o => o.InvoiceDataId);
|
||||||
|
builder.Entity<PaymentData>()
|
||||||
|
.Property(o => o.Status)
|
||||||
|
.HasConversion<string>();
|
||||||
if (databaseFacade.IsNpgsql())
|
if (databaseFacade.IsNpgsql())
|
||||||
{
|
{
|
||||||
builder.Entity<PaymentData>()
|
builder.Entity<PaymentData>()
|
||||||
.Property(o => o.Blob2)
|
.Property(o => o.Blob2)
|
||||||
.HasColumnType("JSONB");
|
.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;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
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")
|
b.Property<string>("Id")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Amount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<bool>("Archived")
|
b.Property<bool>("Archived")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
@ -259,6 +262,9 @@ namespace BTCPayServer.Migrations
|
||||||
b.Property<DateTimeOffset>("Created")
|
b.Property<DateTimeOffset>("Created")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("CustomerEmail")
|
b.Property<string>("CustomerEmail")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
@ -505,15 +511,27 @@ namespace BTCPayServer.Migrations
|
||||||
b.Property<bool>("Accounted")
|
b.Property<bool>("Accounted")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Amount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<byte[]>("Blob")
|
b.Property<byte[]>("Blob")
|
||||||
.HasColumnType("BLOB");
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
b.Property<string>("Blob2")
|
b.Property<string>("Blob2")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("InvoiceDataId")
|
b.Property<string>("InvoiceDataId")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Type")
|
b.Property<string>("Type")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ using BTCPayServer.Plugins.PointOfSale;
|
||||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
|
@ -172,12 +173,13 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
// Now let's check that no data has been lost in the process
|
// Now let's check that no data has been lost in the process
|
||||||
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
|
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
|
||||||
var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks)
|
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||||
.OfType<DerivationSchemeSettings>().First(o => o.PaymentId.IsBTCOnChain);
|
var onchainBTC = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
var network = handlers.GetBitcoinHandler("BTC").Network;
|
||||||
FastTests.GetParsers().TryParseWalletFile(content, onchainBTC.Network, out var expected, out var error);
|
FastTests.GetParsers().TryParseWalletFile(content, network, out var expected, out var error);
|
||||||
Assert.Equal(expected.ToJson(), onchainBTC.ToJson());
|
var handler = handlers[pmi];
|
||||||
|
Assert.Equal(JToken.FromObject(expected, handler.Serializer), JToken.FromObject(onchainBTC, handler.Serializer));
|
||||||
Assert.Null(error);
|
Assert.Null(error);
|
||||||
|
|
||||||
// Let's check that the root hdkey and account key path are taken into account when making a PSBT
|
// 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 cashCow = tester.LTCExplorerNode;
|
||||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||||
var firstPayment = Money.Coins(0.1m);
|
var firstPayment = Money.Coins(0.1m);
|
||||||
|
var firstDue = invoice.CryptoInfo[0].Due;
|
||||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
{
|
{
|
||||||
|
@ -381,7 +384,7 @@ namespace BTCPayServer.Tests
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
invoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
|
invoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
|
||||||
Assert.Equal("confirmed", invoice.Status);
|
Assert.Equal("complete", invoice.Status);
|
||||||
});
|
});
|
||||||
|
|
||||||
// BTC crash by 50%
|
// BTC crash by 50%
|
||||||
|
@ -829,13 +832,13 @@ normal:
|
||||||
Assert.Single(btcOnlyInvoice.CryptoInfo);
|
Assert.Single(btcOnlyInvoice.CryptoInfo);
|
||||||
Assert.Equal("BTC",
|
Assert.Equal("BTC",
|
||||||
btcOnlyInvoice.CryptoInfo.First().CryptoCode);
|
btcOnlyInvoice.CryptoInfo.First().CryptoCode);
|
||||||
Assert.Equal(PaymentTypes.BTCLike.ToString(),
|
Assert.Equal("BTC-CHAIN",
|
||||||
btcOnlyInvoice.CryptoInfo.First().PaymentType);
|
btcOnlyInvoice.CryptoInfo.First().PaymentType);
|
||||||
|
|
||||||
Assert.Equal(2, normalInvoice.CryptoInfo.Length);
|
Assert.Equal(2, normalInvoice.CryptoInfo.Length);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
normalInvoice.CryptoInfo,
|
normalInvoice.CryptoInfo,
|
||||||
s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
|
s => "BTC-CHAIN" == s.PaymentType && new[] { "BTC", "LTC" }.Contains(
|
||||||
s.CryptoCode));
|
s.CryptoCode));
|
||||||
|
|
||||||
//test topup option
|
//test topup option
|
||||||
|
|
|
@ -19,6 +19,14 @@
|
||||||
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
|
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="TestData\OldInvoices.csv" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="TestData\OldInvoices.csv" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
|
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
|
||||||
|
|
|
@ -162,9 +162,9 @@ namespace BTCPayServer.Tests
|
||||||
s.AddLightningNode();
|
s.AddLightningNode();
|
||||||
s.AddDerivationScheme();
|
s.AddDerivationScheme();
|
||||||
|
|
||||||
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC-LN");
|
||||||
s.GoToInvoiceCheckout(invoiceId);
|
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();
|
s.Driver.Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,8 +210,8 @@ namespace BTCPayServer.Tests
|
||||||
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
|
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
|
||||||
});
|
});
|
||||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
|
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
|
||||||
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
|
.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||||
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
|
.Destination, Network.RegTest),
|
||||||
new Money(0.001m, MoneyUnit.BTC));
|
new Money(0.001m, MoneyUnit.BTC));
|
||||||
|
|
||||||
IWebElement closebutton = null;
|
IWebElement closebutton = null;
|
||||||
|
|
|
@ -62,13 +62,13 @@ namespace BTCPayServer.Tests
|
||||||
var qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
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 clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||||
var payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
var payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||||
var address = 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 .truncate-center-start")).Text);
|
Assert.StartsWith("bcrt", s.Driver.FindElement(By.CssSelector("#Address_BTC-CHAIN .truncate-center-start")).Text);
|
||||||
Assert.DoesNotContain("lightning=", payUrl);
|
Assert.DoesNotContain("lightning=", payUrl);
|
||||||
Assert.Equal($"bitcoin:{address}", payUrl);
|
Assert.Equal($"bitcoin:{address}", payUrl);
|
||||||
Assert.Equal($"bitcoin:{address}", clipboard);
|
Assert.Equal($"bitcoin:{address}", clipboard);
|
||||||
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
|
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
|
// Details should show exchange rate
|
||||||
s.Driver.ToggleCollapse("PaymentDetails");
|
s.Driver.ToggleCollapse("PaymentDetails");
|
||||||
|
@ -84,13 +84,13 @@ namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||||
Assert.StartsWith("lightning:lnurl", payUrl);
|
Assert.StartsWith("lightning:lnurl", payUrl);
|
||||||
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text);
|
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text);
|
||||||
s.Driver.ElementDoesNotExist(By.Id("Address_BTC"));
|
s.Driver.ElementDoesNotExist(By.Id("Address_BTC-CHAIN"));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default payment method
|
// Default payment method
|
||||||
s.GoToHome();
|
s.GoToHome();
|
||||||
invoiceId = s.CreateInvoice(21000, "SATS", defaultPaymentMethod: "BTC_LightningLike");
|
invoiceId = s.CreateInvoice(21000, "SATS", defaultPaymentMethod: "BTC-LN");
|
||||||
s.GoToInvoiceCheckout(invoiceId);
|
s.GoToInvoiceCheckout(invoiceId);
|
||||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||||
Assert.Equal(2, s.Driver.FindElements(By.CssSelector(".payment-method")).Count);
|
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");
|
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
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}", payUrl);
|
||||||
Assert.Equal($"lightning:{address}", clipboard);
|
Assert.Equal($"lightning:{address}", clipboard);
|
||||||
Assert.Equal($"lightning:{address.ToUpperInvariant()}", qrValue);
|
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
|
// Lightning amount in sats
|
||||||
Assert.Contains("BTC", s.Driver.FindElement(By.Id("AmountDue")).Text);
|
Assert.Contains("BTC", s.Driver.FindElement(By.Id("AmountDue")).Text);
|
||||||
|
@ -155,7 +155,7 @@ namespace BTCPayServer.Tests
|
||||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||||
|
|
||||||
await Task.Delay(200);
|
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";
|
var amountFraction = "0.00001";
|
||||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
|
||||||
Money.Parse(amountFraction));
|
Money.Parse(amountFraction));
|
||||||
|
@ -271,8 +271,8 @@ namespace BTCPayServer.Tests
|
||||||
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||||
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_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 .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.StartsWith($"bitcoin:{copyAddressOnchain}?amount=", payUrl);
|
||||||
Assert.Contains("?amount=", payUrl);
|
Assert.Contains("?amount=", payUrl);
|
||||||
Assert.Contains("&lightning=", payUrl);
|
Assert.Contains("&lightning=", payUrl);
|
||||||
|
@ -311,7 +311,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
// BIP21 with LN as default payment method
|
// BIP21 with LN as default payment method
|
||||||
s.GoToHome();
|
s.GoToHome();
|
||||||
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC-LN");
|
||||||
s.GoToInvoiceCheckout(invoiceId);
|
s.GoToInvoiceCheckout(invoiceId);
|
||||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||||
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
|
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");
|
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
|
||||||
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
|
||||||
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
|
||||||
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_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 .truncate-center-start")).Text;
|
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC-CHAIN .truncate-center-start")).Text;
|
||||||
Assert.StartsWith($"bitcoin:{copyAddressOnchain}", payUrl);
|
Assert.StartsWith($"bitcoin:{copyAddressOnchain}", payUrl);
|
||||||
Assert.Contains("?lightning=lnurl", payUrl);
|
Assert.Contains("?lightning=lnurl", payUrl);
|
||||||
Assert.DoesNotContain("amount=", payUrl);
|
Assert.DoesNotContain("amount=", payUrl);
|
||||||
|
@ -414,7 +414,7 @@ namespace BTCPayServer.Tests
|
||||||
// - NFC/LNURL-W available with just Lightning
|
// - NFC/LNURL-W available with just Lightning
|
||||||
// - BIP21 works correctly even though Lightning is default payment method
|
// - BIP21 works correctly even though Lightning is default payment method
|
||||||
s.GoToHome();
|
s.GoToHome();
|
||||||
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
|
invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC-LN");
|
||||||
s.GoToInvoiceCheckout(invoiceId);
|
s.GoToInvoiceCheckout(invoiceId);
|
||||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||||
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
|
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
|
||||||
|
@ -462,8 +462,8 @@ namespace BTCPayServer.Tests
|
||||||
iframe.WaitUntilAvailable(By.Id("Checkout-v2"));
|
iframe.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||||
|
|
||||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
|
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
|
||||||
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
|
.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||||
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
|
.Destination, Network.RegTest),
|
||||||
new Money(0.001m, MoneyUnit.BTC));
|
new Money(0.001m, MoneyUnit.BTC));
|
||||||
|
|
||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -47,6 +48,7 @@ using NBXplorer.DerivationStrategy;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
using OpenQA.Selenium.DevTools.V100.DOMSnapshot;
|
using OpenQA.Selenium.DevTools.V100.DOMSnapshot;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
@ -236,26 +238,24 @@ namespace BTCPayServer.Tests
|
||||||
var id = PaymentMethodId.Parse("BTC");
|
var id = PaymentMethodId.Parse("BTC");
|
||||||
var id1 = PaymentMethodId.Parse("BTC-OnChain");
|
var id1 = PaymentMethodId.Parse("BTC-OnChain");
|
||||||
var id2 = PaymentMethodId.Parse("BTC-BTCLike");
|
var id2 = PaymentMethodId.Parse("BTC-BTCLike");
|
||||||
|
Assert.Equal("LTC-LN", PaymentMethodId.Parse("LTC-LightningNetwork").ToString());
|
||||||
Assert.Equal(id, id1);
|
Assert.Equal(id, id1);
|
||||||
Assert.Equal(id, id2);
|
Assert.Equal(id, id2);
|
||||||
Assert.Equal("BTC", id.ToString());
|
Assert.Equal("BTC-CHAIN", id.ToString());
|
||||||
Assert.Equal("BTC", id.ToString());
|
Assert.Equal("BTC-CHAIN", id.ToString());
|
||||||
id = PaymentMethodId.Parse("LTC");
|
id = PaymentMethodId.Parse("LTC");
|
||||||
Assert.Equal("LTC", id.ToString());
|
Assert.Equal("LTC-CHAIN", id.ToString());
|
||||||
Assert.Equal("LTC", id.ToStringNormalized());
|
|
||||||
id = PaymentMethodId.Parse("LTC-offchain");
|
id = PaymentMethodId.Parse("LTC-offchain");
|
||||||
id1 = PaymentMethodId.Parse("LTC-OffChain");
|
id1 = PaymentMethodId.Parse("LTC-OffChain");
|
||||||
id2 = PaymentMethodId.Parse("LTC-LightningLike");
|
id2 = PaymentMethodId.Parse("LTC-LightningLike");
|
||||||
Assert.Equal(id, id1);
|
Assert.Equal(id, id1);
|
||||||
Assert.Equal(id, id2);
|
Assert.Equal(id, id2);
|
||||||
Assert.Equal("LTC_LightningLike", id.ToString());
|
Assert.Equal("LTC-LN", id.ToString());
|
||||||
Assert.Equal("LTC-LightningNetwork", id.ToStringNormalized());
|
|
||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
id = PaymentMethodId.Parse("XMR");
|
id = PaymentMethodId.Parse("XMR");
|
||||||
id1 = PaymentMethodId.Parse("XMR-MoneroLike");
|
id1 = PaymentMethodId.Parse("XMR-MoneroLike");
|
||||||
Assert.Equal(id, id1);
|
Assert.Equal(id, id1);
|
||||||
Assert.Equal("XMR_MoneroLike", id.ToString());
|
Assert.Equal("XMR-CHAIN", id.ToString());
|
||||||
Assert.Equal("XMR", id.ToStringNormalized());
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,29 +439,31 @@ namespace BTCPayServer.Tests
|
||||||
}}
|
}}
|
||||||
}, out items));
|
}, out items));
|
||||||
}
|
}
|
||||||
|
PaymentMethodId BTC = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||||
|
PaymentMethodId LTC = PaymentTypes.CHAIN.GetPaymentMethodId("LTC");
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanCalculateDust()
|
public void CanCalculateDust()
|
||||||
{
|
{
|
||||||
var entity = new InvoiceEntity() { Currency = "USD" };
|
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||||
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
|
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
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",
|
Currency = "BTC",
|
||||||
Rate = 34_000m
|
Divisibility = 8
|
||||||
});
|
});
|
||||||
entity.Price = 4000;
|
entity.Price = 4000;
|
||||||
entity.UpdateTotals();
|
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
|
// Exact price should be 0.117647059..., but the payment method round up to one sat
|
||||||
Assert.Equal(0.11764706m, accounting.Due);
|
Assert.Equal(0.11764706m, accounting.Due);
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(0.11764706m), new Key()),
|
Value = 0.11764706m,
|
||||||
Accounted = true
|
Status = PaymentStatus.Settled,
|
||||||
});
|
});
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
Assert.Equal(0.0m, entity.NetDue);
|
Assert.Equal(0.0m, entity.NetDue);
|
||||||
|
@ -474,13 +476,13 @@ namespace BTCPayServer.Tests
|
||||||
// Now, imagine there is litecoin. It might seem from its
|
// Now, imagine there is litecoin. It might seem from its
|
||||||
// perspecitve that there has been a slight over payment.
|
// perspecitve that there has been a slight over payment.
|
||||||
// However, Calculate() should just cap it to 0.0m
|
// However, Calculate() should just cap it to 0.0m
|
||||||
entity.SetPaymentMethod(new PaymentMethod()
|
entity.SetPaymentPrompt(LTC, new PaymentPrompt()
|
||||||
{
|
{
|
||||||
Currency = "LTC",
|
Currency = "LTC",
|
||||||
Rate = 3400m
|
Divisibility = 8
|
||||||
});
|
});
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
var method = entity.GetPaymentMethods().First(p => p.Currency == "LTC");
|
var method = entity.GetPaymentPrompts().First(p => p.Currency == "LTC");
|
||||||
accounting = method.Calculate();
|
accounting = method.Calculate();
|
||||||
Assert.Equal(0.0m, accounting.DueUncapped);
|
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||||
|
|
||||||
|
@ -492,19 +494,19 @@ namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
|
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
|
||||||
var entity = new InvoiceEntity() { Currency = "USD" };
|
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||||
entity.Networks = networkProvider;
|
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||||
entity.SetPaymentMethod(new PaymentMethod()
|
entity.Rates["BTC"] = 5000m;
|
||||||
|
entity.SetPaymentPrompt(BTC, new PaymentPrompt()
|
||||||
{
|
{
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
Rate = 5000,
|
PaymentMethodFee = 0.1m,
|
||||||
NextNetworkFee = Money.Coins(0.1m)
|
Divisibility = 8
|
||||||
});
|
});
|
||||||
entity.Price = 5000;
|
entity.Price = 5000;
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
|
|
||||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
var paymentMethod = entity.GetPaymentPrompts().TryGet(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||||
var accounting = paymentMethod.Calculate();
|
var accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(1.0m, accounting.ToSmallestUnit(Money.Satoshis(1.0m).ToDecimal(MoneyUnit.BTC)));
|
Assert.Equal(1.0m, accounting.ToSmallestUnit(Money.Satoshis(1.0m).ToDecimal(MoneyUnit.BTC)));
|
||||||
Assert.Equal(1.1m, accounting.Due);
|
Assert.Equal(1.1m, accounting.Due);
|
||||||
|
@ -513,10 +515,10 @@ namespace BTCPayServer.Tests
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(0.5m), new Key()),
|
Value = 0.5m,
|
||||||
Rate = 5000,
|
Rate = 5000,
|
||||||
Accounted = true,
|
Status = PaymentStatus.Settled,
|
||||||
NetworkFee = 0.1m
|
PaymentMethodFee = 0.1m
|
||||||
});
|
});
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
|
@ -527,9 +529,9 @@ namespace BTCPayServer.Tests
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
Value = 0.2m,
|
||||||
Accounted = true,
|
Status = PaymentStatus.Settled,
|
||||||
NetworkFee = 0.1m
|
PaymentMethodFee = 0.1m
|
||||||
});
|
});
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
|
@ -539,9 +541,9 @@ namespace BTCPayServer.Tests
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(0.6m), new Key()),
|
Value = 0.6m,
|
||||||
Accounted = true,
|
Status = PaymentStatus.Settled,
|
||||||
NetworkFee = 0.1m
|
PaymentMethodFee = 0.1m
|
||||||
});
|
});
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
|
@ -549,75 +551,79 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal(1.3m, accounting.TotalDue);
|
Assert.Equal(1.3m, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(
|
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();
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(0.0m, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
Assert.Equal(1.3m, accounting.TotalDue);
|
Assert.Equal(1.3m, accounting.TotalDue);
|
||||||
|
|
||||||
entity = new InvoiceEntity();
|
entity = new InvoiceEntity();
|
||||||
entity.Networks = networkProvider;
|
|
||||||
entity.Price = 5000;
|
entity.Price = 5000;
|
||||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
entity.Currency = "USD";
|
||||||
paymentMethods.Add(
|
entity.Rates["BTC"] = 1000m;
|
||||||
new PaymentMethod() { Currency = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
entity.Rates["LTC"] = 500m;
|
||||||
paymentMethods.Add(
|
PaymentPromptDictionary paymentMethods =
|
||||||
new PaymentMethod() { Currency = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m) });
|
[
|
||||||
entity.SetPaymentMethods(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.Payments = new List<PaymentEntity>();
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentPrompt(BTC);
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(5.1m, accounting.Due);
|
Assert.Equal(5.1m, accounting.Due);
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentPrompt(LTC);
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
|
|
||||||
Assert.Equal(10.01m, accounting.TotalDue);
|
Assert.Equal(10.01m, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
|
PaymentMethodId = BTC,
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
Value = 1.0m,
|
||||||
Accounted = true,
|
Status = PaymentStatus.Settled,
|
||||||
NetworkFee = 0.1m
|
PaymentMethodFee = 0.1m
|
||||||
});
|
});
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(4.2m, accounting.Due);
|
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(1.0m, accounting.Paid);
|
||||||
Assert.Equal(5.2m, accounting.TotalDue);
|
Assert.Equal(5.2m, accounting.TotalDue);
|
||||||
Assert.Equal(2, accounting.TxRequired);
|
Assert.Equal(2, accounting.TxRequired);
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("LTC"));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(10.01m + 0.1m * 2 - 2.0m /* 8.21m */, accounting.Due);
|
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(2.0m, accounting.Paid);
|
||||||
Assert.Equal(10.01m + 0.1m * 2, accounting.TotalDue);
|
Assert.Equal(10.01m + 0.1m * 2, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
|
PaymentMethodId = LTC,
|
||||||
Currency = "LTC",
|
Currency = "LTC",
|
||||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
Value = 1.0m,
|
||||||
Accounted = true,
|
Status = PaymentStatus.Settled,
|
||||||
NetworkFee = 0.01m
|
PaymentMethodFee = 0.01m
|
||||||
});
|
});
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(4.2m - 0.5m + 0.01m / 2, accounting.Due);
|
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(1.5m, accounting.Paid);
|
||||||
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue); // The fee for LTC added
|
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue); // The fee for LTC added
|
||||||
Assert.Equal(2, accounting.TxRequired);
|
Assert.Equal(2, accounting.TxRequired);
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("LTC"));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(8.21m - 1.0m + 0.01m, accounting.Due);
|
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(3.0m, accounting.Paid);
|
||||||
Assert.Equal(10.01m + 0.1m * 2 + 0.01m, accounting.TotalDue);
|
Assert.Equal(10.01m + 0.1m * 2 + 0.01m, accounting.TotalDue);
|
||||||
Assert.Equal(2, accounting.TxRequired);
|
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);
|
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2.0m).ToDecimal(MoneyUnit.BTC);
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
|
PaymentMethodId = BTC,
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(remaining), new Key()),
|
Value = remaining,
|
||||||
Accounted = true,
|
Status = PaymentStatus.Settled,
|
||||||
NetworkFee = 0.1m
|
PaymentMethodFee = 0.1m
|
||||||
});
|
});
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(0.0m, accounting.Due);
|
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(1.5m + remaining, accounting.Paid);
|
||||||
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue);
|
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue);
|
||||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||||
Assert.Equal(2, accounting.TxRequired);
|
Assert.Equal(2, accounting.TxRequired);
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentPrompt(PaymentTypes.CHAIN.GetPaymentMethodId("LTC"));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(0.0m, accounting.Due);
|
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);
|
Assert.Equal(3.0m + remaining * 2, accounting.Paid);
|
||||||
// Paying 2 BTC fee, LTC fee removed because fully 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 */,
|
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()
|
public void CanAcceptInvoiceWithTolerance()
|
||||||
{
|
{
|
||||||
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
|
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
|
||||||
var entity = new InvoiceEntity();
|
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||||
entity.Networks = networkProvider;
|
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
entity.Payments = new List<PaymentEntity>();
|
entity.Payments = new List<PaymentEntity>();
|
||||||
entity.SetPaymentMethod(new PaymentMethod()
|
entity.Rates["BTC"] = 5000m;
|
||||||
|
entity.SetPaymentPrompt(BTC, new PaymentPrompt()
|
||||||
{
|
{
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
Rate = 5000,
|
PaymentMethodFee = 0.1m,
|
||||||
NextNetworkFee = Money.Coins(0.1m)
|
Divisibility = 8
|
||||||
});
|
});
|
||||||
entity.Price = 5000;
|
entity.Price = 5000;
|
||||||
entity.PaymentTolerance = 0;
|
entity.PaymentTolerance = 0;
|
||||||
entity.UpdateTotals();
|
entity.UpdateTotals();
|
||||||
|
|
||||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
var paymentMethod = entity.GetPaymentPrompts().TryGet(PaymentTypes.CHAIN.GetPaymentMethodId("BTC"));
|
||||||
var accounting = paymentMethod.Calculate();
|
var accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(1.1m, accounting.Due);
|
Assert.Equal(1.1m, accounting.Due);
|
||||||
Assert.Equal(1.1m, accounting.TotalDue);
|
Assert.Equal(1.1m, accounting.TotalDue);
|
||||||
|
@ -2144,63 +2151,48 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||||
var networkBTC = networkProvider.GetNetwork("BTC");
|
var networkBTC = networkProvider.GetNetwork("BTC");
|
||||||
var networkLTC = networkProvider.GetNetwork("LTC");
|
var networkLTC = networkProvider.GetNetwork("LTC");
|
||||||
InvoiceEntity invoiceEntity = new InvoiceEntity();
|
InvoiceEntity invoiceEntity = new InvoiceEntity();
|
||||||
invoiceEntity.Networks = networkProvider;
|
invoiceEntity.Currency = "USD";
|
||||||
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||||
invoiceEntity.Price = 100;
|
invoiceEntity.Price = 100;
|
||||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
invoiceEntity.Rates.Add("BTC", 10513.44m);
|
||||||
paymentMethods.Add(new PaymentMethod() { Network = networkBTC, Currency = "BTC", Rate = 10513.44m, }
|
invoiceEntity.Rates.Add("LTC", 216.79m);
|
||||||
.SetPaymentMethodDetails(
|
PaymentPromptDictionary paymentMethods =
|
||||||
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
[
|
||||||
{
|
new () { PaymentMethodId = BTC, Divisibility = 8, Currency = "BTC", PaymentMethodFee = 0.00000100m, ParentEntity = invoiceEntity },
|
||||||
NextNetworkFee = Money.Coins(0.00000100m),
|
new () { PaymentMethodId = LTC, Divisibility = 8, Currency = "LTC", PaymentMethodFee = 0.00010000m, ParentEntity = invoiceEntity },
|
||||||
DepositAddress = dummy
|
];
|
||||||
}));
|
invoiceEntity.SetPaymentPrompts(paymentMethods);
|
||||||
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);
|
|
||||||
|
|
||||||
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
var btcId = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||||
|
var btc = invoiceEntity.GetPaymentPrompt(btcId);
|
||||||
var accounting = btc.Calculate();
|
var accounting = btc.Calculate();
|
||||||
|
|
||||||
invoiceEntity.Payments.Add(
|
invoiceEntity.Payments.Add(
|
||||||
new PaymentEntity()
|
new PaymentEntity()
|
||||||
{
|
{
|
||||||
Accounted = true,
|
Status = PaymentStatus.Settled,
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
NetworkFee = 0.00000100m,
|
PaymentMethodFee = 0.00000100m,
|
||||||
Network = networkProvider.GetNetwork("BTC"),
|
Value = 0.00151263m,
|
||||||
}
|
PaymentMethodId = btcId
|
||||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
});
|
||||||
{
|
|
||||||
Network = networkProvider.GetNetwork("BTC"),
|
|
||||||
Output = new TxOut() { Value = Money.Coins(0.00151263m) }
|
|
||||||
}));
|
|
||||||
invoiceEntity.UpdateTotals();
|
invoiceEntity.UpdateTotals();
|
||||||
accounting = btc.Calculate();
|
accounting = btc.Calculate();
|
||||||
invoiceEntity.Payments.Add(
|
invoiceEntity.Payments.Add(
|
||||||
new PaymentEntity()
|
new PaymentEntity()
|
||||||
{
|
{
|
||||||
Accounted = true,
|
Status = PaymentStatus.Settled,
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
NetworkFee = 0.00000100m,
|
Value = accounting.Due,
|
||||||
Network = networkProvider.GetNetwork("BTC")
|
PaymentMethodFee = 0.00000100m,
|
||||||
}
|
PaymentMethodId = btcId
|
||||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
});
|
||||||
{
|
|
||||||
Network = networkProvider.GetNetwork("BTC"),
|
|
||||||
Output = new TxOut() { Value = Money.Coins(accounting.Due) }
|
|
||||||
}));
|
|
||||||
invoiceEntity.UpdateTotals();
|
invoiceEntity.UpdateTotals();
|
||||||
accounting = btc.Calculate();
|
accounting = btc.Calculate();
|
||||||
Assert.Equal(0.0m, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
Assert.Equal(0.0m, accounting.DueUncapped);
|
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();
|
accounting = ltc.Calculate();
|
||||||
|
|
||||||
Assert.Equal(0.0m, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
|
@ -2248,42 +2240,172 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||||
Assert.Null(metadata.PosData);
|
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]
|
[Fact]
|
||||||
public void CanParseInvoiceEntityDerivationStrategies()
|
public void CanParseInvoiceEntityDerivationStrategies()
|
||||||
{
|
{
|
||||||
|
var serializer = BlobSerializer.CreateSerializer(new NBXplorer.NBXplorerNetworkProvider(ChainName.Regtest).GetBTC()).Serializer;
|
||||||
// We have 3 ways of serializing the derivation strategies:
|
// We have 3 ways of serializing the derivation strategies:
|
||||||
// through "derivationStrategy", through "derivationStrategies" as a string, through "derivationStrategies" as JObject
|
// through "derivationStrategy", through "derivationStrategies" as a string, through "derivationStrategies" as JObject
|
||||||
// Let's check that InvoiceEntity is similar in all cases.
|
// Let's check that InvoiceEntity is similar in all cases.
|
||||||
var legacy = new JObject()
|
var legacy = new JObject()
|
||||||
{
|
{
|
||||||
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf"
|
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf-[p2sh]"
|
||||||
};
|
};
|
||||||
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", CreateNetworkProvider(ChainName.Regtest).BTC);
|
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf-[p2sh]", CreateNetworkProvider(ChainName.Regtest).BTC);
|
||||||
Assert.True(scheme.AccountDerivation is DirectDerivationStrategy { Segwit: true });
|
Assert.True(scheme.AccountDerivation is P2SHDerivationStrategy);
|
||||||
scheme.Source = "ManualDerivationScheme";
|
scheme.Source = "ManualDerivationScheme";
|
||||||
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf";
|
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf-[p2sh]";
|
||||||
var legacy2 = new JObject()
|
var legacy2 = new JObject()
|
||||||
{
|
{
|
||||||
["derivationStrategies"] = scheme.ToJson()
|
["derivationStrategies"] = new JObject()
|
||||||
|
{
|
||||||
|
["BTC"] = JToken.FromObject(scheme, serializer)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var newformat = new JObject()
|
var newformat = new JObject()
|
||||||
{
|
{
|
||||||
["derivationStrategies"] = JObject.Parse(scheme.ToJson())
|
["derivationStrategies"] = new JObject()
|
||||||
|
{
|
||||||
|
["BTC"] = JToken.FromObject(scheme, serializer)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//new BTCPayNetworkProvider(ChainName.Regtest)
|
//new BTCPayNetworkProvider(ChainName.Regtest)
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||||
var formats = new[] { legacy, legacy2, newformat }
|
var formats = new[] { legacy, legacy2, newformat }
|
||||||
.Select(o =>
|
.Select(o =>
|
||||||
{
|
{
|
||||||
var entity = JsonConvert.DeserializeObject<InvoiceEntity>(o.ToString());
|
o.Add("currency", "USD");
|
||||||
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
|
o.Add("price", "0.0");
|
||||||
return entity.DerivationStrategies.ToString();
|
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();
|
.ToHashSet();
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
var v = Assert.Single(formats);
|
||||||
Assert.Single(formats);
|
Assert.NotNull(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -2292,25 +2414,8 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||||
var pmi = "\"BTC_hasjdfhasjkfjlajn\"";
|
var pmi = "\"BTC_hasjdfhasjkfjlajn\"";
|
||||||
JsonTextReader reader = new(new StringReader(pmi));
|
JsonTextReader reader = new(new StringReader(pmi));
|
||||||
reader.Read();
|
reader.Read();
|
||||||
Assert.Null(new PaymentMethodIdJsonConverter().ReadJson(reader, typeof(PaymentMethodId), null,
|
Assert.Equal("BTC-hasjdfhasjkfjlajn", new PaymentMethodIdJsonConverter().ReadJson(reader, typeof(PaymentMethodId), null,
|
||||||
JsonSerializer.CreateDefault()));
|
JsonSerializer.CreateDefault()).ToString());
|
||||||
}
|
|
||||||
|
|
||||||
[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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ using Newtonsoft.Json.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using Xunit.Sdk;
|
using Xunit.Sdk;
|
||||||
|
using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
|
@ -228,7 +229,7 @@ namespace BTCPayServer.Tests
|
||||||
await Assert.ThrowsAsync<GreenfieldAPIException>(() => newUserClient.GetInvoices(store.Id));
|
await Assert.ThrowsAsync<GreenfieldAPIException>(() => newUserClient.GetInvoices(store.Id));
|
||||||
|
|
||||||
// if user is a guest or owner, then it should be ok
|
// 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);
|
await newUserClient.GetInvoices(store.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -947,7 +948,7 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal(payout.Id, payout2.Id);
|
Assert.Equal(payout.Id, payout2.Id);
|
||||||
Assert.Equal(destination, payout2.Destination);
|
Assert.Equal(destination, payout2.Destination);
|
||||||
Assert.Equal(PayoutState.AwaitingApproval, payout.State);
|
Assert.Equal(PayoutState.AwaitingApproval, payout.State);
|
||||||
Assert.Equal("BTC", payout2.PaymentMethod);
|
Assert.Equal("BTC-CHAIN", payout2.PaymentMethod);
|
||||||
Assert.Equal("BTC", payout2.CryptoCode);
|
Assert.Equal("BTC", payout2.CryptoCode);
|
||||||
Assert.Null(payout.PaymentMethodAmount);
|
Assert.Null(payout.PaymentMethodAmount);
|
||||||
|
|
||||||
|
@ -1239,7 +1240,7 @@ namespace BTCPayServer.Tests
|
||||||
PaymentMethod = "BTC",
|
PaymentMethod = "BTC",
|
||||||
Amount = 0.0001m,
|
Amount = 0.0001m,
|
||||||
Destination = address.ToString(),
|
Destination = address.ToString(),
|
||||||
|
|
||||||
});
|
});
|
||||||
await AssertAPIError("invalid-state", async () =>
|
await AssertAPIError("invalid-state", async () =>
|
||||||
{
|
{
|
||||||
|
@ -1393,7 +1394,7 @@ namespace BTCPayServer.Tests
|
||||||
//check that pmc equals the one we set
|
//check that pmc equals the one we set
|
||||||
Assert.Equal(10, pmc.Amount);
|
Assert.Equal(10, pmc.Amount);
|
||||||
Assert.True(pmc.Above);
|
Assert.True(pmc.Above);
|
||||||
Assert.Equal("BTC", pmc.PaymentMethod);
|
Assert.Equal("BTC-CHAIN", pmc.PaymentMethod);
|
||||||
Assert.Equal("USD", pmc.CurrencyCode);
|
Assert.Equal("USD", pmc.CurrencyCode);
|
||||||
updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" });
|
updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" });
|
||||||
Assert.Empty(newStore.PaymentMethodCriteria);
|
Assert.Empty(newStore.PaymentMethodCriteria);
|
||||||
|
@ -1428,7 +1429,7 @@ namespace BTCPayServer.Tests
|
||||||
// We strip the user's Owner right, so the key should not work
|
// We strip the user's Owner right, so the key should not work
|
||||||
using var ctx = tester.PayTester.GetService<Data.ApplicationDbContextFactory>().CreateContext();
|
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 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;
|
storeEntity.StoreRoleId = roleId;
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
await AssertHttpError(403, async () => await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" }));
|
await AssertHttpError(403, async () => await client.UpdateStore(newStore.Id, new UpdateStoreRequest() { Name = "B" }));
|
||||||
|
@ -1441,7 +1442,7 @@ namespace BTCPayServer.Tests
|
||||||
}
|
}
|
||||||
tester.DeleteStore = false;
|
tester.DeleteStore = false;
|
||||||
Assert.Empty(await client.GetStores());
|
Assert.Empty(await client.GetStores());
|
||||||
|
|
||||||
// Archive
|
// Archive
|
||||||
var archivableStore = await client.CreateStore(new CreateStoreRequest { Name = "Archivable" });
|
var archivableStore = await client.CreateStore(new CreateStoreRequest { Name = "Archivable" });
|
||||||
Assert.False(archivableStore.Archived);
|
Assert.False(archivableStore.Archived);
|
||||||
|
@ -1676,8 +1677,8 @@ namespace BTCPayServer.Tests
|
||||||
Assert.NotNull(serverInfoData.Version);
|
Assert.NotNull(serverInfoData.Version);
|
||||||
Assert.NotNull(serverInfoData.Onion);
|
Assert.NotNull(serverInfoData.Onion);
|
||||||
Assert.True(serverInfoData.FullySynched);
|
Assert.True(serverInfoData.FullySynched);
|
||||||
Assert.Contains("BTC", serverInfoData.SupportedPaymentMethods);
|
Assert.Contains("BTC-CHAIN", serverInfoData.SupportedPaymentMethods);
|
||||||
Assert.Contains("BTC_LightningLike", serverInfoData.SupportedPaymentMethods);
|
Assert.Contains("BTC-LN", serverInfoData.SupportedPaymentMethods);
|
||||||
Assert.NotNull(serverInfoData.SyncStatus);
|
Assert.NotNull(serverInfoData.SyncStatus);
|
||||||
Assert.Single(serverInfoData.SyncStatus.Select(s => s.CryptoCode == "BTC"));
|
Assert.Single(serverInfoData.SyncStatus.Select(s => s.CryptoCode == "BTC"));
|
||||||
}
|
}
|
||||||
|
@ -1779,7 +1780,7 @@ namespace BTCPayServer.Tests
|
||||||
await tester.ExplorerNode.GenerateAsync(1);
|
await tester.ExplorerNode.GenerateAsync(1);
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
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)
|
if (!partialPayment)
|
||||||
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
|
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()
|
await client.RefundInvoice(user.StoreId, "lol fake invoice id", new RefundInvoiceRequest()
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.RateThen
|
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
|
// 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()
|
var apiError = await AssertAPIError("non-refundable", () => client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.RateThen
|
RefundVariant = RefundVariant.RateThen
|
||||||
}));
|
}));
|
||||||
Assert.Equal("Cannot refund this invoice", apiError.Message);
|
Assert.Equal("Cannot refund this invoice", apiError.Message);
|
||||||
|
@ -2020,7 +2021,7 @@ namespace BTCPayServer.Tests
|
||||||
// test RefundVariant.RateThen
|
// test RefundVariant.RateThen
|
||||||
var pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
var pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.RateThen
|
RefundVariant = RefundVariant.RateThen
|
||||||
});
|
});
|
||||||
Assert.Equal("BTC", pp.Currency);
|
Assert.Equal("BTC", pp.Currency);
|
||||||
|
@ -2031,7 +2032,7 @@ namespace BTCPayServer.Tests
|
||||||
// test RefundVariant.CurrentRate
|
// test RefundVariant.CurrentRate
|
||||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.CurrentRate
|
RefundVariant = RefundVariant.CurrentRate
|
||||||
});
|
});
|
||||||
Assert.Equal("BTC", pp.Currency);
|
Assert.Equal("BTC", pp.Currency);
|
||||||
|
@ -2041,7 +2042,7 @@ namespace BTCPayServer.Tests
|
||||||
// test RefundVariant.Fiat
|
// test RefundVariant.Fiat
|
||||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.Fiat,
|
RefundVariant = RefundVariant.Fiat,
|
||||||
Name = "my test name"
|
Name = "my test name"
|
||||||
});
|
});
|
||||||
|
@ -2055,7 +2056,7 @@ namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.Custom,
|
RefundVariant = RefundVariant.Custom,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2064,7 +2065,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.Custom,
|
RefundVariant = RefundVariant.Custom,
|
||||||
CustomAmount = 69420,
|
CustomAmount = 69420,
|
||||||
CustomCurrency = "JPY"
|
CustomCurrency = "JPY"
|
||||||
|
@ -2076,19 +2077,19 @@ namespace BTCPayServer.Tests
|
||||||
// should auto-approve if currencies match
|
// should auto-approve if currencies match
|
||||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest()
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.Custom,
|
RefundVariant = RefundVariant.Custom,
|
||||||
CustomAmount = 0.00069420m,
|
CustomAmount = 0.00069420m,
|
||||||
CustomCurrency = "BTC"
|
CustomCurrency = "BTC"
|
||||||
});
|
});
|
||||||
Assert.True(pp.AutoApproveClaims);
|
Assert.True(pp.AutoApproveClaims);
|
||||||
|
|
||||||
// test subtract percentage
|
// test subtract percentage
|
||||||
validationError = await AssertValidationError(new[] { "SubtractPercentage" }, async () =>
|
validationError = await AssertValidationError(new[] { "SubtractPercentage" }, async () =>
|
||||||
{
|
{
|
||||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.RateThen,
|
RefundVariant = RefundVariant.RateThen,
|
||||||
SubtractPercentage = 101
|
SubtractPercentage = 101
|
||||||
});
|
});
|
||||||
|
@ -2098,25 +2099,25 @@ namespace BTCPayServer.Tests
|
||||||
// should auto-approve
|
// should auto-approve
|
||||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.RateThen,
|
RefundVariant = RefundVariant.RateThen,
|
||||||
SubtractPercentage = 6.15m
|
SubtractPercentage = 6.15m
|
||||||
});
|
});
|
||||||
Assert.Equal("BTC", pp.Currency);
|
Assert.Equal("BTC", pp.Currency);
|
||||||
Assert.True(pp.AutoApproveClaims);
|
Assert.True(pp.AutoApproveClaims);
|
||||||
Assert.Equal(0.9385m, pp.Amount);
|
Assert.Equal(0.9385m, pp.Amount);
|
||||||
|
|
||||||
// test RefundVariant.OverpaidAmount
|
// test RefundVariant.OverpaidAmount
|
||||||
validationError = await AssertValidationError(new[] { "RefundVariant" }, async () =>
|
validationError = await AssertValidationError(new[] { "RefundVariant" }, async () =>
|
||||||
{
|
{
|
||||||
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.OverpaidAmount
|
RefundVariant = RefundVariant.OverpaidAmount
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Assert.Contains("Invoice is not overpaid", validationError.Message);
|
Assert.Contains("Invoice is not overpaid", validationError.Message);
|
||||||
|
|
||||||
// should auto-approve
|
// should auto-approve
|
||||||
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest { Amount = 5000.0m, Currency = "USD" });
|
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest { Amount = 5000.0m, Currency = "USD" });
|
||||||
methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id);
|
methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id);
|
||||||
|
@ -2134,24 +2135,24 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
|
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
|
||||||
Assert.True(invoice.Status == InvoiceStatus.Settled);
|
Assert.True(invoice.Status == InvoiceStatus.Settled);
|
||||||
Assert.True(invoice.AdditionalStatus == InvoiceExceptionStatus.PaidOver);
|
Assert.True(invoice.AdditionalStatus == InvoiceExceptionStatus.PaidOver);
|
||||||
});
|
});
|
||||||
|
|
||||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.OverpaidAmount
|
RefundVariant = RefundVariant.OverpaidAmount
|
||||||
});
|
});
|
||||||
Assert.Equal("BTC", pp.Currency);
|
Assert.Equal("BTC", pp.Currency);
|
||||||
Assert.True(pp.AutoApproveClaims);
|
Assert.True(pp.AutoApproveClaims);
|
||||||
Assert.Equal(method.Due, pp.Amount);
|
Assert.Equal(method.Due, pp.Amount);
|
||||||
|
|
||||||
// once more with subtract percentage
|
// once more with subtract percentage
|
||||||
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
pp = await client.RefundInvoice(user.StoreId, invoice.Id, new RefundInvoiceRequest
|
||||||
{
|
{
|
||||||
PaymentMethod = method.PaymentMethod,
|
PaymentMethod = method.PaymentMethodId,
|
||||||
RefundVariant = RefundVariant.OverpaidAmount,
|
RefundVariant = RefundVariant.OverpaidAmount,
|
||||||
SubtractPercentage = 21m
|
SubtractPercentage = 21m
|
||||||
});
|
});
|
||||||
|
@ -2287,8 +2288,8 @@ namespace BTCPayServer.Tests
|
||||||
var paymentMethods = await viewOnly.GetInvoicePaymentMethods(user.StoreId, newInvoice.Id);
|
var paymentMethods = await viewOnly.GetInvoicePaymentMethods(user.StoreId, newInvoice.Id);
|
||||||
Assert.Single(paymentMethods);
|
Assert.Single(paymentMethods);
|
||||||
var paymentMethod = paymentMethods.First();
|
var paymentMethod = paymentMethods.First();
|
||||||
Assert.Equal("BTC", paymentMethod.PaymentMethod);
|
Assert.Equal("BTC-CHAIN", paymentMethod.PaymentMethodId);
|
||||||
Assert.Equal("BTC", paymentMethod.CryptoCode);
|
Assert.Equal("BTC", paymentMethod.Currency);
|
||||||
Assert.Empty(paymentMethod.Payments);
|
Assert.Empty(paymentMethod.Payments);
|
||||||
|
|
||||||
|
|
||||||
|
@ -2455,7 +2456,7 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Single(paymentMethods);
|
Assert.Single(paymentMethods);
|
||||||
Assert.False(paymentMethods.First().Activated);
|
Assert.False(paymentMethods.First().Activated);
|
||||||
await client.ActivateInvoicePaymentMethod(user.StoreId, invoice.Id,
|
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);
|
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
|
||||||
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "address");
|
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "address");
|
||||||
|
|
||||||
|
@ -2474,7 +2475,7 @@ namespace BTCPayServer.Tests
|
||||||
DefaultPaymentMethod = "BTC_LightningLike"
|
DefaultPaymentMethod = "BTC_LightningLike"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Assert.Equal("BTC_LightningLike", invoiceWithDefaultPaymentMethodLN.Checkout.DefaultPaymentMethod);
|
Assert.Equal("BTC-LN", invoiceWithDefaultPaymentMethodLN.Checkout.DefaultPaymentMethod);
|
||||||
|
|
||||||
var invoiceWithDefaultPaymentMethodOnChain = await client.CreateInvoice(user.StoreId,
|
var invoiceWithDefaultPaymentMethodOnChain = await client.CreateInvoice(user.StoreId,
|
||||||
new CreateInvoiceRequest()
|
new CreateInvoiceRequest()
|
||||||
|
@ -2487,19 +2488,19 @@ namespace BTCPayServer.Tests
|
||||||
DefaultPaymentMethod = "BTC"
|
DefaultPaymentMethod = "BTC"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Assert.Equal("BTC", invoiceWithDefaultPaymentMethodOnChain.Checkout.DefaultPaymentMethod);
|
Assert.Equal("BTC-CHAIN", invoiceWithDefaultPaymentMethodOnChain.Checkout.DefaultPaymentMethod);
|
||||||
|
|
||||||
// reset lazy payment methods
|
// reset lazy payment methods
|
||||||
store = await client.GetStore(user.StoreId);
|
store = await client.GetStore(user.StoreId);
|
||||||
store.LazyPaymentMethods = false;
|
store.LazyPaymentMethods = false;
|
||||||
store = await client.UpdateStore(store.Id,
|
store = await client.UpdateStore(store.Id,
|
||||||
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
||||||
Assert.False(store.LazyPaymentMethods);
|
Assert.False(store.LazyPaymentMethods);
|
||||||
|
|
||||||
// use store default payment method
|
// use store default payment method
|
||||||
store = await client.GetStore(user.StoreId);
|
store = await client.GetStore(user.StoreId);
|
||||||
Assert.Null(store.DefaultPaymentMethod);
|
Assert.Null(store.DefaultPaymentMethod);
|
||||||
var storeDefaultPaymentMethod = "BTC-LightningNetwork";
|
var storeDefaultPaymentMethod = "BTC-LN";
|
||||||
store.DefaultPaymentMethod = storeDefaultPaymentMethod;
|
store.DefaultPaymentMethod = storeDefaultPaymentMethod;
|
||||||
store = await client.UpdateStore(store.Id,
|
store = await client.UpdateStore(store.Id,
|
||||||
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
||||||
|
@ -2515,7 +2516,7 @@ namespace BTCPayServer.Tests
|
||||||
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
PaymentMethods = new[] { "BTC", "BTC-LightningNetwork", "BTC_LightningLike" }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Assert.Equal(storeDefaultPaymentMethod, invoiceWithStoreDefaultPaymentMethod.Checkout.DefaultPaymentMethod);
|
Assert.Null(invoiceWithStoreDefaultPaymentMethod.Checkout.DefaultPaymentMethod);
|
||||||
|
|
||||||
//let's see the overdue amount
|
//let's see the overdue amount
|
||||||
invoice = await client.CreateInvoice(user.StoreId,
|
invoice = await client.CreateInvoice(user.StoreId,
|
||||||
|
@ -2724,8 +2725,8 @@ namespace BTCPayServer.Tests
|
||||||
Amount = 100,
|
Amount = 100,
|
||||||
Checkout = new CreateInvoiceRequest.CheckoutOptions
|
Checkout = new CreateInvoiceRequest.CheckoutOptions
|
||||||
{
|
{
|
||||||
PaymentMethods = new[] { "BTC-LightningNetwork" },
|
PaymentMethods = new[] { "BTC-LN" },
|
||||||
DefaultPaymentMethod = "BTC_LightningLike"
|
DefaultPaymentMethod = "BTC-LN"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2751,11 +2752,13 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal(PayResult.Ok, resp.Result);
|
Assert.Equal(PayResult.Ok, resp.Result);
|
||||||
Assert.NotNull(resp.Details.PaymentHash);
|
Assert.NotNull(resp.Details.PaymentHash);
|
||||||
Assert.NotNull(resp.Details.Preimage);
|
Assert.NotNull(resp.Details.Preimage);
|
||||||
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
pm[i] = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, (await invoices[i]).Id));
|
{
|
||||||
Assert.True(pm[i].AdditionalData.HasValues);
|
pm[i] = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, (await invoices[i]).Id));
|
||||||
Assert.Equal(resp.Details.PaymentHash.ToString(), pm[i].AdditionalData.GetValue("paymentHash"));
|
Assert.True(pm[i].AdditionalData.HasValues);
|
||||||
Assert.Equal(resp.Details.Preimage.ToString(), pm[i].AdditionalData.GetValue("preimage"));
|
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" });
|
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 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()
|
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");
|
await client.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC");
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC",
|
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC", xpub)).Addresses.First().Address);
|
||||||
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);
|
|
||||||
|
|
||||||
|
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);
|
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewStoreOnChainPaymentMethodAddresses(store.Id, "BTC")).Addresses.First().Address);
|
||||||
await AssertHttpError(403, async () =>
|
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 AssertHttpError(404, async () =>
|
||||||
{
|
{
|
||||||
await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
|
await client.GetStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||||
});
|
});
|
||||||
|
|
||||||
await AssertHttpError(403, async () =>
|
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");
|
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",
|
var generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, });
|
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, });
|
||||||
|
|
||||||
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||||
Assert.Equal(generateResponse.DerivationScheme, xpub);
|
Assert.Equal(generateResponse.Config.AccountDerivation, xpub);
|
||||||
|
|
||||||
await AssertAPIError("already-configured", async () =>
|
await AssertAPIError("already-configured", async () =>
|
||||||
{
|
{
|
||||||
|
@ -2936,22 +2922,22 @@ namespace BTCPayServer.Tests
|
||||||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, });
|
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, });
|
||||||
});
|
});
|
||||||
|
|
||||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
await client.RemoveStorePaymentMethod(store.Id, "BTC-CHAIN");
|
||||||
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||||
new GenerateOnChainWalletRequest() { });
|
new GenerateOnChainWalletRequest() { });
|
||||||
Assert.NotEqual(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
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",
|
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, AccountNumber = 1 });
|
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, AccountNumber = 1 });
|
||||||
|
|
||||||
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||||
|
|
||||||
Assert.Equal(new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
|
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",
|
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||||
new GenerateOnChainWalletRequest() { WordList = Wordlist.Japanese, WordCount = WordCount.TwentyFour });
|
new GenerateOnChainWalletRequest() { WordList = Wordlist.Japanese, WordCount = WordCount.TwentyFour });
|
||||||
|
|
||||||
|
@ -2978,26 +2964,29 @@ namespace BTCPayServer.Tests
|
||||||
var viewOnlyClient = await admin.CreateClient(Policies.CanViewStoreSettings);
|
var viewOnlyClient = await admin.CreateClient(Policies.CanViewStoreSettings);
|
||||||
var store = await adminClient.GetStore(admin.StoreId);
|
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 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 AssertHttpError(404, async () =>
|
||||||
{
|
{
|
||||||
await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
|
await adminClient.GetStorePaymentMethod(store.Id, "BTC-LN");
|
||||||
});
|
});
|
||||||
await admin.RegisterLightningNodeAsync("BTC", false);
|
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 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 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 () =>
|
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
|
Enabled = true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Assert.Contains("btcpay.server.canmodifyserversettings", ex.Message);
|
Assert.Contains("btcpay.server.canmodifyserversettings", ex.Message);
|
||||||
// However, the other client should work because he has `btcpay.server.canmodifyserversettings`
|
// 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
|
Enabled = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Allowed ip should be ok
|
// 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
|
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
|
// 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 admin2.MakeAdmin(false);
|
||||||
await AssertValidationError(new[] { "ConnectionString" }, async () =>
|
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
|
Enabled = true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3056,52 +3057,72 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
await AssertHttpError(404, async () =>
|
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,
|
Enabled = method.Enabled,
|
||||||
ConnectionString = method.ConnectionString
|
Config = new JObject()
|
||||||
|
{
|
||||||
|
["internalNodeRef"] = "Internal Node"
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
settings = await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>();
|
settings = await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>();
|
||||||
settings.AllowLightningInternalNodeForAll = true;
|
settings.AllowLightningInternalNodeForAll = true;
|
||||||
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
|
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,
|
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
|
// 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 = (await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
||||||
settings.AllowLightningInternalNodeForAll = false;
|
settings.AllowLightningInternalNodeForAll = false;
|
||||||
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
|
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,
|
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,
|
Enabled = true,
|
||||||
ConnectionString = "Internal Node"
|
Config = new JObject()
|
||||||
|
{
|
||||||
|
["connectionString"] = "Internal Node"
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
// NonAdmin add admin as owner of the store
|
// NonAdmin add admin as owner of the store
|
||||||
await nonAdminUser.AddOwner(admin.UserId);
|
await nonAdminUser.AddOwner(admin.UserId);
|
||||||
// Admin turn on Internal node
|
// Admin turn on Internal node
|
||||||
adminClient = await admin.CreateClient(Policies.CanModifyStoreSettings, Policies.CanUseInternalLightningNode);
|
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,
|
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.
|
// 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,
|
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));
|
Assert.Empty(await adminClient.GetStorePaymentMethods(store.Id));
|
||||||
|
|
||||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(admin.StoreId, "BTC", new UpdateLightningNetworkPaymentMethodRequest("Internal Node", true));
|
await adminClient.UpdateStorePaymentMethod(admin.StoreId, "BTC-LN", new UpdatePaymentMethodRequest()
|
||||||
|
|
||||||
void VerifyLightning(Dictionary<string, GenericPaymentMethodData> dictionary)
|
|
||||||
{
|
{
|
||||||
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
|
Enabled = true,
|
||||||
var lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
Config = new JObject()
|
||||||
Assert.Equal("Internal Node", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
{
|
||||||
|
{"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);
|
Assert.Single(methods);
|
||||||
VerifyLightning(methods);
|
VerifyLightning(methods);
|
||||||
|
|
||||||
var randK = new Mnemonic(Wordlist.English, WordCount.Twelve).DeriveExtKey().Neuter().ToString(Network.RegTest);
|
var wallet = await adminClient.GenerateOnChainWallet(store.Id, "BTC", new GenerateOnChainWalletRequest() { });
|
||||||
await adminClient.UpdateStoreOnChainPaymentMethod(admin.StoreId, "BTC",
|
|
||||||
new UpdateOnChainPaymentMethodRequest(true, randK, "testing", null));
|
|
||||||
|
|
||||||
void VerifyOnChain(Dictionary<string, GenericPaymentMethodData> dictionary)
|
void VerifyOnChain(GenericPaymentMethodData[] dictionary)
|
||||||
{
|
{
|
||||||
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), out var item));
|
var m = Assert.Single(methods.Where(m => m.PaymentMethodId == "BTC-CHAIN"));
|
||||||
var paymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
|
var paymentMethodBaseData = Assert.IsType<JObject>(m.Config);
|
||||||
Assert.Equal(randK, paymentMethodBaseData.DerivationScheme);
|
Assert.Equal(wallet.Config.AccountDerivation, paymentMethodBaseData["accountDerivation"].Value<string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
methods = await adminClient.GetStorePaymentMethods(store.Id);
|
methods = await adminClient.GetStorePaymentMethods(store.Id, includeConfig: true);
|
||||||
Assert.Equal(2, methods.Count);
|
Assert.Equal(2, methods.Length);
|
||||||
VerifyLightning(methods);
|
VerifyLightning(methods);
|
||||||
VerifyOnChain(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);
|
Assert.Equal(connStr, methods.FirstOrDefault(m => m.PaymentMethodId == "BTC-LN")?.Config["connectionString"].Value<string>());
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
methods = await adminClient.GetStorePaymentMethods(store.Id);
|
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));
|
// Alternative way of setting the connection string
|
||||||
lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
await adminClient.UpdateStorePaymentMethod(store.Id, "BTC-LN",
|
||||||
Assert.NotEqual("*NEED CanModifyStoreSettings PERMISSION TO VIEW*", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
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)]
|
[Fact(Timeout = TestTimeout)]
|
||||||
|
@ -3657,11 +3686,11 @@ namespace BTCPayServer.Tests
|
||||||
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
var adminClient = await admin.CreateClient(Policies.Unrestricted);
|
||||||
Assert.False((await adminClient.GetUserByIdOrEmail(admin.UserId)).RequiresApproval);
|
Assert.False((await adminClient.GetUserByIdOrEmail(admin.UserId)).RequiresApproval);
|
||||||
Assert.Empty(await adminClient.GetNotifications());
|
Assert.Empty(await adminClient.GetNotifications());
|
||||||
|
|
||||||
// require approval
|
// require approval
|
||||||
var settings = tester.PayTester.GetService<SettingsRepository>();
|
var settings = tester.PayTester.GetService<SettingsRepository>();
|
||||||
await settings.UpdateSetting(new PoliciesSettings { LockSubscription = false, RequiresUserApproval = true });
|
await settings.UpdateSetting(new PoliciesSettings { LockSubscription = false, RequiresUserApproval = true });
|
||||||
|
|
||||||
// new user needs approval
|
// new user needs approval
|
||||||
var unapprovedUser = tester.NewAccount();
|
var unapprovedUser = tester.NewAccount();
|
||||||
await unapprovedUser.GrantAccessAsync();
|
await unapprovedUser.GrantAccessAsync();
|
||||||
|
@ -3684,7 +3713,7 @@ namespace BTCPayServer.Tests
|
||||||
Assert.True((await adminClient.GetUserByIdOrEmail(unapprovedUser.UserId)).Approved);
|
Assert.True((await adminClient.GetUserByIdOrEmail(unapprovedUser.UserId)).Approved);
|
||||||
Assert.True((await unapprovedUserApiKeyClient.GetCurrentUser()).Approved);
|
Assert.True((await unapprovedUserApiKeyClient.GetCurrentUser()).Approved);
|
||||||
Assert.True((await unapprovedUserBasicAuthClient.GetCurrentUser()).Approved);
|
Assert.True((await unapprovedUserBasicAuthClient.GetCurrentUser()).Approved);
|
||||||
|
|
||||||
// un-approve
|
// un-approve
|
||||||
Assert.True(await adminClient.ApproveUser(unapprovedUser.UserId, false, CancellationToken.None));
|
Assert.True(await adminClient.ApproveUser(unapprovedUser.UserId, false, CancellationToken.None));
|
||||||
Assert.False((await adminClient.GetUserByIdOrEmail(unapprovedUser.UserId)).Approved);
|
Assert.False((await adminClient.GetUserByIdOrEmail(unapprovedUser.UserId)).Approved);
|
||||||
|
@ -3696,10 +3725,10 @@ namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
await unapprovedUserBasicAuthClient.GetCurrentUser();
|
await unapprovedUserBasicAuthClient.GetCurrentUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
// reset policies to not require approval
|
// reset policies to not require approval
|
||||||
await settings.UpdateSetting(new PoliciesSettings { LockSubscription = false, RequiresUserApproval = false });
|
await settings.UpdateSetting(new PoliciesSettings { LockSubscription = false, RequiresUserApproval = false });
|
||||||
|
|
||||||
// new user does not need approval
|
// new user does not need approval
|
||||||
var newUser = tester.NewAccount();
|
var newUser = tester.NewAccount();
|
||||||
await newUser.GrantAccessAsync();
|
await newUser.GrantAccessAsync();
|
||||||
|
@ -3710,14 +3739,14 @@ namespace BTCPayServer.Tests
|
||||||
Assert.False((await newUserBasicAuthClient.GetCurrentUser()).RequiresApproval);
|
Assert.False((await newUserBasicAuthClient.GetCurrentUser()).RequiresApproval);
|
||||||
Assert.False((await newUserBasicAuthClient.GetCurrentUser()).Approved);
|
Assert.False((await newUserBasicAuthClient.GetCurrentUser()).Approved);
|
||||||
Assert.Single(await adminClient.GetNotifications(false));
|
Assert.Single(await adminClient.GetNotifications(false));
|
||||||
|
|
||||||
// try unapproving user which does not have the RequiresApproval flag
|
// try unapproving user which does not have the RequiresApproval flag
|
||||||
await AssertAPIError("invalid-state", async () =>
|
await AssertAPIError("invalid-state", async () =>
|
||||||
{
|
{
|
||||||
await adminClient.ApproveUser(newUser.UserId, false, CancellationToken.None);
|
await adminClient.ApproveUser(newUser.UserId, false, CancellationToken.None);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Timeout = 60 * 2 * 1000)]
|
[Fact(Timeout = 60 * 2 * 1000)]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
[Trait("Lightning", "Lightning")]
|
[Trait("Lightning", "Lightning")]
|
||||||
|
@ -3773,7 +3802,7 @@ namespace BTCPayServer.Tests
|
||||||
Amount = 0.0001m,
|
Amount = 0.0001m,
|
||||||
Metadata = JObject.FromObject(new
|
Metadata = JObject.FromObject(new
|
||||||
{
|
{
|
||||||
source ="apitest",
|
source = "apitest",
|
||||||
sourceLink = "https://chocolate.com"
|
sourceLink = "https://chocolate.com"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -3782,16 +3811,16 @@ namespace BTCPayServer.Tests
|
||||||
source = "apitest",
|
source = "apitest",
|
||||||
sourceLink = "https://chocolate.com"
|
sourceLink = "https://chocolate.com"
|
||||||
}).ToString());
|
}).ToString());
|
||||||
|
|
||||||
payout =
|
payout =
|
||||||
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
|
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
|
||||||
|
|
||||||
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
|
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
|
||||||
{
|
{
|
||||||
source = "apitest",
|
source = "apitest",
|
||||||
sourceLink = "https://chocolate.com"
|
sourceLink = "https://chocolate.com"
|
||||||
}).ToString());
|
}).ToString());
|
||||||
|
|
||||||
customerInvoice = await tester.CustomerLightningD.CreateInvoice(LightMoney.FromUnit(10, LightMoneyUnit.Satoshi),
|
customerInvoice = await tester.CustomerLightningD.CreateInvoice(LightMoney.FromUnit(10, LightMoneyUnit.Satoshi),
|
||||||
Guid.NewGuid().ToString(), TimeSpan.FromDays(40));
|
Guid.NewGuid().ToString(), TimeSpan.FromDays(40));
|
||||||
var payout2 = await adminClient.CreatePayout(admin.StoreId,
|
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);
|
Assert.Equal(3600, Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC")).IntervalSeconds.TotalSeconds);
|
||||||
|
|
||||||
var tpGen = Assert.Single(await adminClient.GetPayoutProcessors(admin.StoreId));
|
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
|
//still too poor to process any payouts
|
||||||
Assert.Empty(await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
Assert.Empty(await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC"));
|
||||||
|
|
||||||
|
@ -3921,10 +3950,10 @@ namespace BTCPayServer.Tests
|
||||||
uint256 txid = null;
|
uint256 txid = null;
|
||||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
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);
|
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(0.01m) + fee);
|
||||||
}, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
}, 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 () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
Assert.Equal(4, (await adminClient.ShowOnChainWalletTransactions(admin.StoreId, "BTC")).Count());
|
||||||
|
@ -3935,24 +3964,24 @@ namespace BTCPayServer.Tests
|
||||||
// settings that were added later
|
// settings that were added later
|
||||||
var settings =
|
var settings =
|
||||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||||
Assert.False( settings.ProcessNewPayoutsInstantly);
|
Assert.False(settings.ProcessNewPayoutsInstantly);
|
||||||
Assert.Equal(0m, settings.Threshold);
|
Assert.Equal(0m, settings.Threshold);
|
||||||
|
|
||||||
//let's use the ProcessNewPayoutsInstantly so that it will trigger instantly
|
//let's use the ProcessNewPayoutsInstantly so that it will trigger instantly
|
||||||
|
|
||||||
settings.IntervalSeconds = TimeSpan.FromDays(1);
|
settings.IntervalSeconds = TimeSpan.FromDays(1);
|
||||||
settings.ProcessNewPayoutsInstantly = true;
|
settings.ProcessNewPayoutsInstantly = true;
|
||||||
|
|
||||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
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);
|
tester.ExplorerClient.Network.NBitcoinNetwork), Money.Coins(1m) + fee);
|
||||||
}, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
}, correctEvent: ev => ev.NewTransactionEvent.TransactionData.TransactionHash == txid);
|
||||||
|
|
||||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", settings);
|
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", settings);
|
||||||
settings =
|
settings =
|
||||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||||
Assert.True( settings.ProcessNewPayoutsInstantly);
|
Assert.True(settings.ProcessNewPayoutsInstantly);
|
||||||
|
|
||||||
var pluginHookService = tester.PayTester.GetService<IPluginHookService>();
|
var pluginHookService = tester.PayTester.GetService<IPluginHookService>();
|
||||||
var beforeHookTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
var beforeHookTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
@ -4008,23 +4037,23 @@ namespace BTCPayServer.Tests
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeHookTcs = new TaskCompletionSource();
|
beforeHookTcs = new TaskCompletionSource();
|
||||||
afterHookTcs = new TaskCompletionSource();
|
afterHookTcs = new TaskCompletionSource();
|
||||||
//let's test the threshold limiter
|
//let's test the threshold limiter
|
||||||
settings.Threshold = 0.5m;
|
settings.Threshold = 0.5m;
|
||||||
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", settings);
|
await adminClient.UpdateStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC", settings);
|
||||||
|
|
||||||
//quick test: when updating processor, it processes instantly
|
//quick test: when updating processor, it processes instantly
|
||||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
settings =
|
settings =
|
||||||
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
Assert.Single(await adminClient.GetStoreOnChainAutomatedPayoutProcessors(admin.StoreId, "BTC"));
|
||||||
Assert.Equal(0.5m, settings.Threshold);
|
Assert.Equal(0.5m, settings.Threshold);
|
||||||
|
|
||||||
//create a payout that should not be processed straight away due to threshold
|
//create a payout that should not be processed straight away due to threshold
|
||||||
|
|
||||||
beforeHookTcs = new TaskCompletionSource();
|
beforeHookTcs = new TaskCompletionSource();
|
||||||
afterHookTcs = new TaskCompletionSource();
|
afterHookTcs = new TaskCompletionSource();
|
||||||
var payoutThatShouldNotBeProcessedStraightAway = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
var payoutThatShouldNotBeProcessedStraightAway = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||||
|
@ -4034,13 +4063,13 @@ namespace BTCPayServer.Tests
|
||||||
PaymentMethod = "BTC",
|
PaymentMethod = "BTC",
|
||||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||||
});
|
});
|
||||||
|
|
||||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||||
Assert.Single(payouts.Where(data => data.State == PayoutState.AwaitingPayment && data.Id == payoutThatShouldNotBeProcessedStraightAway.Id));
|
Assert.Single(payouts.Where(data => data.State == PayoutState.AwaitingPayment && data.Id == payoutThatShouldNotBeProcessedStraightAway.Id));
|
||||||
|
|
||||||
beforeHookTcs = new TaskCompletionSource();
|
beforeHookTcs = new TaskCompletionSource();
|
||||||
afterHookTcs = new TaskCompletionSource();
|
afterHookTcs = new TaskCompletionSource();
|
||||||
var payoutThatShouldNotBeProcessedStraightAway2 = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
var payoutThatShouldNotBeProcessedStraightAway2 = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||||
|
@ -4050,14 +4079,14 @@ namespace BTCPayServer.Tests
|
||||||
PaymentMethod = "BTC",
|
PaymentMethod = "BTC",
|
||||||
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
|
||||||
});
|
});
|
||||||
|
|
||||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||||
Assert.Equal(2, payouts.Count(data => data.State == PayoutState.AwaitingPayment &&
|
Assert.Equal(2, payouts.Count(data => data.State == PayoutState.AwaitingPayment &&
|
||||||
(data.Id == payoutThatShouldNotBeProcessedStraightAway.Id || data.Id == payoutThatShouldNotBeProcessedStraightAway2.Id)));
|
(data.Id == payoutThatShouldNotBeProcessedStraightAway.Id || data.Id == payoutThatShouldNotBeProcessedStraightAway2.Id)));
|
||||||
|
|
||||||
beforeHookTcs = new TaskCompletionSource();
|
beforeHookTcs = new TaskCompletionSource();
|
||||||
afterHookTcs = new TaskCompletionSource();
|
afterHookTcs = new TaskCompletionSource();
|
||||||
await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
|
||||||
|
@ -4070,7 +4099,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
payouts = await adminClient.GetStorePayouts(admin.StoreId);
|
||||||
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
Assert.Empty(payouts.Where(data => data.State != PayoutState.InProgress));
|
||||||
|
|
||||||
|
|
|
@ -381,6 +381,7 @@ namespace BTCPayServer.Tests
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
});
|
});
|
||||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||||
|
var handler = s.Server.PayTester.GetService<PaymentMethodHandlerDictionary>().GetBitcoinHandler("BTC");
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
var invoice = await invoiceRepository.GetInvoice(invoiceId);
|
var invoice = await invoiceRepository.GetInvoice(invoiceId);
|
||||||
|
@ -389,19 +390,13 @@ namespace BTCPayServer.Tests
|
||||||
var originalPayment = payments[0];
|
var originalPayment = payments[0];
|
||||||
var coinjoinPayment = payments[1];
|
var coinjoinPayment = payments[1];
|
||||||
Assert.Equal(-1,
|
Assert.Equal(-1,
|
||||||
((BitcoinLikePaymentData)originalPayment.GetCryptoPaymentData()).ConfirmationCount);
|
handler.ParsePaymentDetails(originalPayment.Details).ConfirmationCount);
|
||||||
Assert.Equal(0,
|
Assert.Equal(0,
|
||||||
((BitcoinLikePaymentData)coinjoinPayment.GetCryptoPaymentData()).ConfirmationCount);
|
handler.ParsePaymentDetails(coinjoinPayment.Details).ConfirmationCount);
|
||||||
Assert.False(originalPayment.Accounted);
|
Assert.False(originalPayment.Accounted);
|
||||||
Assert.True(coinjoinPayment.Accounted);
|
Assert.True(coinjoinPayment.Accounted);
|
||||||
Assert.Equal(((BitcoinLikePaymentData)originalPayment.GetCryptoPaymentData()).Value,
|
Assert.Equal(originalPayment.Value,
|
||||||
((BitcoinLikePaymentData)coinjoinPayment.GetCryptoPaymentData()).Value);
|
coinjoinPayment.Value);
|
||||||
Assert.Equal(originalPayment.GetCryptoPaymentData()
|
|
||||||
.AssertType<BitcoinLikePaymentData>()
|
|
||||||
.Value,
|
|
||||||
coinjoinPayment.GetCryptoPaymentData()
|
|
||||||
.AssertType<BitcoinLikePaymentData>()
|
|
||||||
.Value);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
|
@ -929,10 +924,9 @@ retry:
|
||||||
tester.ExplorerClient.Network.NBitcoinNetwork);
|
tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||||
|
|
||||||
var senderStore = await tester.PayTester.StoreRepository.FindStore(senderUser.StoreId);
|
var senderStore = await tester.PayTester.StoreRepository.FindStore(senderUser.StoreId);
|
||||||
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||||
var derivationSchemeSettings = senderStore.GetSupportedPaymentMethods(tester.NetworkProvider)
|
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||||
.OfType<DerivationSchemeSettings>().SingleOrDefault(settings =>
|
var derivationSchemeSettings = senderStore.GetPaymentMethodConfig<DerivationSchemeSettings>(paymentMethodId, handlers);
|
||||||
settings.PaymentId == paymentMethodId);
|
|
||||||
|
|
||||||
ReceivedCoin[] senderCoins = null;
|
ReceivedCoin[] senderCoins = null;
|
||||||
ReceivedCoin coin = null;
|
ReceivedCoin coin = null;
|
||||||
|
@ -1138,14 +1132,14 @@ retry:
|
||||||
//broadcast the payjoin
|
//broadcast the payjoin
|
||||||
var res = (await tester.ExplorerClient.BroadcastAsync(Invoice7Coin6Response1TxSigned));
|
var res = (await tester.ExplorerClient.BroadcastAsync(Invoice7Coin6Response1TxSigned));
|
||||||
Assert.True(res.Success);
|
Assert.True(res.Success);
|
||||||
|
var handler = handlers.GetBitcoinHandler("BTC");
|
||||||
// Paid with coinjoin
|
// Paid with coinjoin
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||||
Assert.Equal(InvoiceStatusLegacy.Paid, invoiceEntity.Status);
|
Assert.Equal(InvoiceStatusLegacy.Paid, invoiceEntity.Status);
|
||||||
Assert.Contains(invoiceEntity.GetPayments(false), p => p.Accounted &&
|
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);
|
////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);
|
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
|
||||||
Assert.Equal(InvoiceStatusLegacy.New, invoiceEntity.Status);
|
Assert.Equal(InvoiceStatusLegacy.New, invoiceEntity.Status);
|
||||||
Assert.True(invoiceEntity.GetPayments(false).All(p => !p.Accounted));
|
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>();
|
var payjoinRepository = tester.PayTester.GetService<UTXOLocker>();
|
||||||
// The outpoint should now be available for next pj selection
|
// The outpoint should now be available for next pj selection
|
||||||
|
|
|
@ -27,6 +27,7 @@ using BTCPayServer.Views.Wallets;
|
||||||
using ExchangeSharp;
|
using ExchangeSharp;
|
||||||
using LNURL;
|
using LNURL;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.DataEncoders;
|
using NBitcoin.DataEncoders;
|
||||||
|
@ -1550,17 +1551,16 @@ namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.0m));
|
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 targetTx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.2m));
|
||||||
var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);
|
var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);
|
||||||
var spentOutpoint = new OutPoint(targetTx,
|
var spentOutpoint = new OutPoint(targetTx,
|
||||||
tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
|
tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
|
||||||
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
var store = await s.Server.PayTester.StoreRepository.FindStore(storeId);
|
var store = await s.Server.PayTester.StoreRepository.FindStore(storeId);
|
||||||
var x = store.GetSupportedPaymentMethods(s.Server.NetworkProvider)
|
var x = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||||
.OfType<DerivationSchemeSettings>()
|
|
||||||
.Single(settings => settings.PaymentId.CryptoCode == walletId.CryptoCode);
|
|
||||||
var wallet = s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode);
|
var wallet = s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode);
|
||||||
wallet.InvalidateCache(x.AccountDerivation);
|
wallet.InvalidateCache(x.AccountDerivation);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
|
@ -1821,7 +1821,8 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
var invoiceId = s.CreateInvoice(storeId);
|
var invoiceId = s.CreateInvoice(storeId);
|
||||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
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.
|
//wallet should have been imported to bitcoin core wallet in watch only mode.
|
||||||
var result =
|
var result =
|
||||||
|
@ -1833,7 +1834,7 @@ namespace BTCPayServer.Tests
|
||||||
//lets import and save private keys
|
//lets import and save private keys
|
||||||
invoiceId = s.CreateInvoice(storeId);
|
invoiceId = s.CreateInvoice(storeId);
|
||||||
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||||
address = invoice.EntityToDTO().Addresses["BTC"];
|
address = invoice.GetPaymentPrompt(btc).Destination;
|
||||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(
|
result = await s.Server.ExplorerNode.GetAddressInfoAsync(
|
||||||
BitcoinAddress.Create(address, Network.RegTest));
|
BitcoinAddress.Create(address, Network.RegTest));
|
||||||
//spendable from bitcoin core wallet!
|
//spendable from bitcoin core wallet!
|
||||||
|
@ -1895,8 +1896,7 @@ namespace BTCPayServer.Tests
|
||||||
Assert.EndsWith("psbt/ready", s.Driver.Url);
|
Assert.EndsWith("psbt/ready", s.Driver.Url);
|
||||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||||
Assert.Equal(walletTransactionUri.ToString(), s.Driver.Url);
|
Assert.Equal(walletTransactionUri.ToString(), s.Driver.Url);
|
||||||
|
var bip21 = invoice.EntityToDTO(s.Server.PayTester.GetService<Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension>>()).CryptoInfo.First().PaymentUrls.BIP21;
|
||||||
var bip21 = invoice.EntityToDTO().CryptoInfo.First().PaymentUrls.BIP21;
|
|
||||||
//let's make bip21 more interesting
|
//let's make bip21 more interesting
|
||||||
bip21 += "&label=Solid Snake&message=Snake? Snake? SNAAAAKE!";
|
bip21 += "&label=Solid Snake&message=Snake? Snake? SNAAAAKE!";
|
||||||
var parsedBip21 = new BitcoinUrlBuilder(bip21, Network.RegTest);
|
var parsedBip21 = new BitcoinUrlBuilder(bip21, Network.RegTest);
|
||||||
|
@ -2257,7 +2257,7 @@ namespace BTCPayServer.Tests
|
||||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
|
||||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
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.Id($"{PayoutState.AwaitingApproval}-view")).Click();
|
||||||
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||||
|
@ -2267,7 +2267,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
s.FindAlertMessage();
|
s.FindAlertMessage();
|
||||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
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();
|
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||||
if (!s.Driver.PageSource.Contains(bolt))
|
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.ClassName("mass-action-select-all")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).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();
|
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||||
Assert.Contains(bolt, s.Driver.PageSource);
|
Assert.Contains(bolt, s.Driver.PageSource);
|
||||||
|
@ -2889,6 +2889,7 @@ namespace BTCPayServer.Tests
|
||||||
public async Task CanUseLNURL()
|
public async Task CanUseLNURL()
|
||||||
{
|
{
|
||||||
using var s = CreateSeleniumTester();
|
using var s = CreateSeleniumTester();
|
||||||
|
s.Server.DeleteStore = false;
|
||||||
s.Server.ActivateLightning();
|
s.Server.ActivateLightning();
|
||||||
await s.StartAsync();
|
await s.StartAsync();
|
||||||
await s.Server.EnsureChannelsSetup();
|
await s.Server.EnsureChannelsSetup();
|
||||||
|
@ -3028,7 +3029,7 @@ namespace BTCPayServer.Tests
|
||||||
// Check that pull payment has lightning option
|
// Check that pull payment has lightning option
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
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("Name")).SendKeys("PP1");
|
||||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
||||||
|
@ -3053,7 +3054,7 @@ namespace BTCPayServer.Tests
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||||
payouts[0].Click();
|
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")));
|
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
||||||
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||||
|
@ -3164,14 +3165,14 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal(2, invoices.Length);
|
Assert.Equal(2, invoices.Length);
|
||||||
foreach (var i in invoices)
|
foreach (var i in invoices)
|
||||||
{
|
{
|
||||||
var lightningPaymentMethod = i.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.LNURLPay));
|
var prompt = i.GetPaymentPrompt(PaymentTypes.LNURL.GetPaymentMethodId("BTC"));
|
||||||
var paymentMethodDetails =
|
var handlers = s.Server.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||||
lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
var details = (LNURLPayPaymentMethodDetails)handlers.ParsePaymentPromptDetails(prompt);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
paymentMethodDetails.ConsumedLightningAddress,
|
details.ConsumedLightningAddress,
|
||||||
new[] { lnaddress1, lnaddress2 });
|
new[] { lnaddress1, lnaddress2 });
|
||||||
|
|
||||||
if (paymentMethodDetails.ConsumedLightningAddress == lnaddress2)
|
if (details.ConsumedLightningAddress == lnaddress2)
|
||||||
{
|
{
|
||||||
Assert.Equal("lol", i.Metadata.AdditionalData["test"].Value<string>());
|
Assert.Equal("lol", i.Metadata.AdditionalData["test"].Value<string>());
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
string connectionString = null;
|
string connectionString = null;
|
||||||
if (connectionType is null)
|
if (connectionType is null)
|
||||||
return LightningSupportedPaymentMethod.InternalNode;
|
return LightningPaymentMethodConfig.InternalNode;
|
||||||
if (connectionType == LightningConnectionType.CLightning)
|
if (connectionType == LightningConnectionType.CLightning)
|
||||||
{
|
{
|
||||||
if (isMerchant)
|
if (isMerchant)
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
|
@ -18,9 +19,11 @@ using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Lightning.CLightning;
|
using BTCPayServer.Lightning.CLightning;
|
||||||
using BTCPayServer.Models.AccountViewModels;
|
using BTCPayServer.Models.AccountViewModels;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Payments.PayJoin.Sender;
|
using BTCPayServer.Payments.PayJoin.Sender;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
|
@ -29,6 +32,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.CodeAnalysis.Operations;
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
using NBitcoin.Payment;
|
using NBitcoin.Payment;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
|
@ -148,15 +152,6 @@ namespace BTCPayServer.Tests
|
||||||
await storeController.GeneralSettings(settings);
|
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)
|
public async Task ModifyOnchainPaymentSettings(Action<WalletSettingsViewModel> modify)
|
||||||
{
|
{
|
||||||
var storeController = GetController<UIStoresController>();
|
var storeController = GetController<UIStoresController>();
|
||||||
|
@ -164,6 +159,7 @@ namespace BTCPayServer.Tests
|
||||||
WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model;
|
WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model;
|
||||||
modify(walletSettings);
|
modify(walletSettings);
|
||||||
storeController.UpdatePaymentSettings(walletSettings).GetAwaiter().GetResult();
|
storeController.UpdatePaymentSettings(walletSettings).GetAwaiter().GetResult();
|
||||||
|
storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||||
|
@ -295,7 +291,7 @@ namespace BTCPayServer.Tests
|
||||||
var storeController = GetController<UIStoresController>();
|
var storeController = GetController<UIStoresController>();
|
||||||
|
|
||||||
var connectionString = parent.GetLightningConnectionString(connectionType, isMerchant);
|
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 };
|
var vm = new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true };
|
||||||
await storeController.SetupLightningNode(storeId ?? StoreId,
|
await storeController.SetupLightningNode(storeId ?? StoreId,
|
||||||
|
@ -373,8 +369,9 @@ namespace BTCPayServer.Tests
|
||||||
var pjClient = parent.PayTester.GetService<PayjoinClient>();
|
var pjClient = parent.PayTester.GetService<PayjoinClient>();
|
||||||
var storeRepository = parent.PayTester.GetService<StoreRepository>();
|
var storeRepository = parent.PayTester.GetService<StoreRepository>();
|
||||||
var store = await storeRepository.FindStore(StoreId);
|
var store = await storeRepository.FindStore(StoreId);
|
||||||
var settings = store.GetSupportedPaymentMethods(parent.NetworkProvider).OfType<DerivationSchemeSettings>()
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(psbt.Network.NetworkSet.CryptoCode);
|
||||||
.First();
|
var handlers = parent.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||||
|
var settings = store.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, handlers);
|
||||||
TestLogs.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
|
TestLogs.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
|
||||||
if (expectedError is null && !senderError)
|
if (expectedError is null && !senderError)
|
||||||
{
|
{
|
||||||
|
@ -491,7 +488,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
public class DummyStoreWebhookEvent : StoreWebhookEvent
|
public class DummyStoreWebhookEvent : StoreWebhookEvent
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<StoreWebhookEvent> WebhookEvents { get; set; } = new List<StoreWebhookEvent>();
|
public List<StoreWebhookEvent> WebhookEvents { get; set; } = new List<StoreWebhookEvent>();
|
||||||
|
@ -576,9 +573,10 @@ retry:
|
||||||
public async Task<uint256> PayOnChain(string invoiceId)
|
public async Task<uint256> PayOnChain(string invoiceId)
|
||||||
{
|
{
|
||||||
var cryptoCode = "BTC";
|
var cryptoCode = "BTC";
|
||||||
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||||
var client = await CreateClient();
|
var client = await CreateClient();
|
||||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
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 address = method.Destination;
|
||||||
var tx = await client.CreateOnChainTransaction(StoreId, cryptoCode, new CreateOnChainTransactionRequest()
|
var tx = await client.CreateOnChainTransaction(StoreId, cryptoCode, new CreateOnChainTransactionRequest()
|
||||||
{
|
{
|
||||||
|
@ -601,7 +599,7 @@ retry:
|
||||||
var cryptoCode = "BTC";
|
var cryptoCode = "BTC";
|
||||||
var client = await CreateClient();
|
var client = await CreateClient();
|
||||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
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;
|
var bolt11 = method.Destination;
|
||||||
TestLogs.LogInformation("PAYING");
|
TestLogs.LogInformation("PAYING");
|
||||||
await parent.CustomerLightningD.Pay(bolt11);
|
await parent.CustomerLightningD.Pay(bolt11);
|
||||||
|
@ -615,7 +613,7 @@ retry:
|
||||||
var network = SupportedNetwork.NBitcoinNetwork;
|
var network = SupportedNetwork.NBitcoinNetwork;
|
||||||
var client = await CreateClient();
|
var client = await CreateClient();
|
||||||
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
|
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 lnurL = LNURL.LNURL.Parse(method.PaymentLink, out var tag);
|
||||||
var http = new HttpClient();
|
var http = new HttpClient();
|
||||||
var payreq = (LNURL.LNURLPayRequest)await LNURL.LNURL.FetchInformation(lnurL, tag, http);
|
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 dbContext = this.parent.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||||
var db = (NpgsqlConnection)dbContext.Database.GetDbConnection();
|
var db = (NpgsqlConnection)dbContext.Database.GetDbConnection();
|
||||||
await db.OpenAsync();
|
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"))
|
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)
|
foreach (var invoice in oldInvoices)
|
||||||
{
|
{
|
||||||
var localInvoice = invoice.Replace("3sgUCCtUBg6S8LJkrbdfAWbsJMqByFLfvSqjG6xKBWEd", storeId);
|
if (isHeader)
|
||||||
await writer.WriteLineAsync(localInvoice);
|
{
|
||||||
|
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();
|
await writer.FlushAsync();
|
||||||
}
|
}
|
||||||
|
isHeader = true;
|
||||||
using (var writer = db.BeginTextImport("COPY \"Payments\" (\"Id\",\"Blob\",\"InvoiceDataId\",\"Accounted\",\"Blob2\",\"Type\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
using (var writer = db.BeginTextImport("COPY \"Payments\" (\"Id\",\"Blob\",\"InvoiceDataId\",\"Accounted\",\"Blob2\",\"Type\") FROM STDIN DELIMITER ',' CSV HEADER"))
|
||||||
{
|
{
|
||||||
foreach (var invoice in oldPayments)
|
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.PayButton;
|
||||||
using BTCPayServer.Plugins.PointOfSale;
|
using BTCPayServer.Plugins.PointOfSale;
|
||||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||||
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Security.Bitpay;
|
using BTCPayServer.Security.Bitpay;
|
||||||
using BTCPayServer.Security.Greenfield;
|
using BTCPayServer.Security.Greenfield;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
@ -69,6 +70,7 @@ using NBXplorer.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Newtonsoft.Json.Schema;
|
using Newtonsoft.Json.Schema;
|
||||||
|
using Npgsql;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using Xunit.Sdk;
|
using Xunit.Sdk;
|
||||||
|
@ -377,14 +379,16 @@ namespace BTCPayServer.Tests
|
||||||
await user.RegisterDerivationSchemeAsync("BTC");
|
await user.RegisterDerivationSchemeAsync("BTC");
|
||||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
||||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
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"));
|
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.0001m, "BTC"));
|
||||||
await tester.WaitForEvent<InvoiceNewPaymentDetailsEvent>(async () =>
|
await tester.WaitForEvent<InvoiceNewPaymentDetailsEvent>(async () =>
|
||||||
{
|
{
|
||||||
await tester.ExplorerNode.SendToAddressAsync(
|
await tester.ExplorerNode.SendToAddressAsync(
|
||||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.00005m));
|
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.00005m), new NBitcoin.RPC.SendToAddressParameters()
|
||||||
}, e => e.InvoiceId == invoice.Id && e.PaymentMethodId.PaymentType == LightningPaymentType.Instance);
|
{
|
||||||
await tester.ExplorerNode.GenerateAsync(1);
|
Replaceable = false
|
||||||
|
});
|
||||||
|
}, e => e.InvoiceId == invoice.Id && e.PaymentMethodId == PaymentTypes.LN.GetPaymentMethodId("BTC"));
|
||||||
Invoice newInvoice = null;
|
Invoice newInvoice = null;
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
|
@ -560,7 +564,7 @@ namespace BTCPayServer.Tests
|
||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.GrantAccess();
|
acc.GrantAccess();
|
||||||
acc.RegisterDerivationScheme("BTC");
|
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
|
var invoice = acc.BitPay.CreateInvoice(new Invoice
|
||||||
{
|
{
|
||||||
Price = 5.0m,
|
Price = 5.0m,
|
||||||
|
@ -1043,16 +1047,17 @@ namespace BTCPayServer.Tests
|
||||||
tx1.ToString(),
|
tx1.ToString(),
|
||||||
}).Result["txid"].Value<string>());
|
}).Result["txid"].Value<string>());
|
||||||
TestLogs.LogInformation($"Bumped with {tx1Bump}");
|
TestLogs.LogInformation($"Bumped with {tx1Bump}");
|
||||||
|
var handler = tester.PayTester.GetService<PaymentMethodHandlerDictionary>().GetBitcoinHandler("BTC");
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
var invoiceEntity = await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id);
|
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();
|
var payments = invoiceEntity.GetPayments(false).ToArray();
|
||||||
Assert.Equal(tx1, btcPayments[0].Outpoint.Hash);
|
Assert.Equal(tx1, btcPayments[0].Outpoint.Hash);
|
||||||
Assert.False(payments[0].Accounted);
|
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.True(payments[1].Accounted);
|
||||||
Assert.Equal(0.0m, payments[1].NetworkFee);
|
Assert.Equal(0.0m, payments[1].PaymentMethodFee);
|
||||||
invoice = user.BitPay.GetInvoice(invoice.Id);
|
invoice = user.BitPay.GetInvoice(invoice.Id);
|
||||||
Assert.Equal(payment1, invoice.BtcPaid);
|
Assert.Equal(payment1, invoice.BtcPaid);
|
||||||
Assert.Equal("paid", invoice.Status);
|
Assert.Equal("paid", invoice.Status);
|
||||||
|
@ -1085,8 +1090,8 @@ namespace BTCPayServer.Tests
|
||||||
Assert.IsType<ViewResult>(await user.GetController<UIInvoiceController>().Invoice(invoice.Id)).Model)
|
Assert.IsType<ViewResult>(await user.GetController<UIInvoiceController>().Invoice(invoice.Id)).Model)
|
||||||
.Payments;
|
.Payments;
|
||||||
Assert.Single(payments);
|
Assert.Single(payments);
|
||||||
var paymentData = payments.First().GetCryptoPaymentData() as BitcoinLikePaymentData;
|
var paymentData = payments.First().Details;
|
||||||
Assert.NotNull(paymentData.KeyPath);
|
Assert.NotNull(paymentData["keyPath"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Timeout = LongRunningTestTimeout)]
|
[Fact(Timeout = LongRunningTestTimeout)]
|
||||||
|
@ -1339,12 +1344,10 @@ namespace BTCPayServer.Tests
|
||||||
var btcmethod = (await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id))[0];
|
var btcmethod = (await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id))[0];
|
||||||
var paid = btcSent;
|
var paid = btcSent;
|
||||||
var invoiceAddress = BitcoinAddress.Create(btcmethod.Destination, cashCow.Network);
|
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))
|
var networkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
|
||||||
.GetPaymentMethods()[btc]
|
.GetPaymentPrompt(btc)
|
||||||
.GetPaymentMethodDetails()
|
.PaymentMethodFee;
|
||||||
.AssertType<BitcoinLikeOnChainPaymentMethod>()
|
|
||||||
.GetNextNetworkFee();
|
|
||||||
if (networkFeeMode != NetworkFeeMode.Always)
|
if (networkFeeMode != NetworkFeeMode.Always)
|
||||||
{
|
{
|
||||||
networkFee = 0.0m;
|
networkFee = 0.0m;
|
||||||
|
@ -1364,7 +1367,7 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal("False", bitpayinvoice.ExceptionStatus.ToString());
|
Assert.Equal("False", bitpayinvoice.ExceptionStatus.ToString());
|
||||||
|
|
||||||
// Check if we index by price correctly once we know it
|
// 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);
|
Assert.Contains(invoices, inv => inv.Id == bitpayinvoice.Id);
|
||||||
}
|
}
|
||||||
catch (JsonSerializationException)
|
catch (JsonSerializationException)
|
||||||
|
@ -1492,15 +1495,15 @@ namespace BTCPayServer.Tests
|
||||||
await user.RegisterLightningNodeAsync("BTC");
|
await user.RegisterLightningNodeAsync("BTC");
|
||||||
|
|
||||||
|
|
||||||
var lnMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToString();
|
var lnMethod = PaymentTypes.LN.GetPaymentMethodId("BTC").ToString();
|
||||||
var btcMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike).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
|
// 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
|
var vm = Assert.IsType<CheckoutAppearanceViewModel>(Assert
|
||||||
.IsType<ViewResult>(user.GetController<UIStoresController>().CheckoutAppearance()).Model);
|
.IsType<ViewResult>(user.GetController<UIStoresController>().CheckoutAppearance()).Model);
|
||||||
Assert.Equal(2, vm.PaymentMethodCriteria.Count);
|
Assert.Equal(2, vm.PaymentMethodCriteria.Count);
|
||||||
var criteria = Assert.Single(vm.PaymentMethodCriteria.Where(m => m.PaymentMethod == btcMethod.ToString()));
|
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.Value = "5 USD";
|
||||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan;
|
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan;
|
||||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm)
|
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm)
|
||||||
|
@ -1518,8 +1521,8 @@ namespace BTCPayServer.Tests
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
// LN and LNURL
|
// LN and LNURL
|
||||||
Assert.Equal(2, invoice.CryptoInfo.Length);
|
Assert.Equal(2, invoice.CryptoInfo.Length);
|
||||||
Assert.Contains(invoice.CryptoInfo, c => c.PaymentType == PaymentTypes.LNURLPay.ToString());
|
Assert.Contains(invoice.CryptoInfo, c => c.PaymentType == "BTC-LNURL");
|
||||||
Assert.Contains(invoice.CryptoInfo, c => c.PaymentType == PaymentTypes.LightningLike.ToString());
|
Assert.Contains(invoice.CryptoInfo, c => c.PaymentType == "BTC-LN");
|
||||||
|
|
||||||
// Let's replicate https://github.com/btcpayserver/btcpayserver/issues/2963
|
// 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
|
// 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);
|
user.SetLNUrl(cryptoCode, false);
|
||||||
var vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
var vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||||
var criteria = Assert.Single(vm.PaymentMethodCriteria);
|
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.Value = "2 USD";
|
||||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.LessThan;
|
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.LessThan;
|
||||||
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm)
|
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm)
|
||||||
|
@ -1652,14 +1655,14 @@ namespace BTCPayServer.Tests
|
||||||
Currency = "USD"
|
Currency = "USD"
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
Assert.Single(invoice.CryptoInfo);
|
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.
|
// Activating LNUrl, we should still have only 1 payment criteria that can be set.
|
||||||
user.RegisterLightningNode(cryptoCode);
|
user.RegisterLightningNode(cryptoCode);
|
||||||
user.SetLNUrl(cryptoCode, true);
|
user.SetLNUrl(cryptoCode, true);
|
||||||
vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>();
|
||||||
criteria = Assert.Single(vm.PaymentMethodCriteria);
|
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);
|
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm).Result);
|
||||||
|
|
||||||
// However, creating an invoice should show LNURL
|
// However, creating an invoice should show LNURL
|
||||||
|
@ -1713,7 +1716,7 @@ namespace BTCPayServer.Tests
|
||||||
public async Task CanChangeNetworkFeeMode()
|
public async Task CanChangeNetworkFeeMode()
|
||||||
{
|
{
|
||||||
using var tester = CreateServerTester();
|
using var tester = CreateServerTester();
|
||||||
var btc = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
|
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||||
await tester.StartAsync();
|
await tester.StartAsync();
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
|
@ -1733,10 +1736,7 @@ namespace BTCPayServer.Tests
|
||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
var nextNetworkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
|
var nextNetworkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
|
||||||
.GetPaymentMethods()[btc]
|
.GetPaymentPrompt(btc).PaymentMethodFee;
|
||||||
.GetPaymentMethodDetails()
|
|
||||||
.AssertType<BitcoinLikeOnChainPaymentMethod>()
|
|
||||||
.GetNextNetworkFee();
|
|
||||||
var firstPaymentFee = nextNetworkFee;
|
var firstPaymentFee = nextNetworkFee;
|
||||||
switch (networkFeeMode)
|
switch (networkFeeMode)
|
||||||
{
|
{
|
||||||
|
@ -1768,10 +1768,8 @@ namespace BTCPayServer.Tests
|
||||||
TestLogs.LogInformation($"Remaining due after first payment: {due}");
|
TestLogs.LogInformation($"Remaining due after first payment: {due}");
|
||||||
Assert.Equal(Money.Coins(firstPayment), Money.Parse(invoice.CryptoInfo[0].Paid));
|
Assert.Equal(Money.Coins(firstPayment), Money.Parse(invoice.CryptoInfo[0].Paid));
|
||||||
nextNetworkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
|
nextNetworkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
|
||||||
.GetPaymentMethods()[btc]
|
.GetPaymentPrompt(btc)
|
||||||
.GetPaymentMethodDetails()
|
.PaymentMethodFee;
|
||||||
.AssertType<BitcoinLikeOnChainPaymentMethod>()
|
|
||||||
.GetNextNetworkFee();
|
|
||||||
switch (networkFeeMode)
|
switch (networkFeeMode)
|
||||||
{
|
{
|
||||||
case NetworkFeeMode.Never:
|
case NetworkFeeMode.Never:
|
||||||
|
@ -1942,10 +1940,10 @@ namespace BTCPayServer.Tests
|
||||||
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
||||||
var entity = (await repo.GetInvoice(invoice6.Id));
|
var entity = (await repo.GetInvoice(invoice6.Id));
|
||||||
Assert.Equal((decimal)ulong.MaxValue, entity.Price);
|
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
|
// Shouldn't be possible as we clamp the value, but existing invoice may have that
|
||||||
entity.Price = decimal.MaxValue;
|
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 =>
|
var invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model =>
|
||||||
PaymentMethodId.Parse(model.PaymentMethod) ==
|
PaymentMethodId.Parse(model.PaymentMethodId) ==
|
||||||
new PaymentMethodId("BTC", BitcoinPaymentType.Instance))
|
PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||||
.PaymentLink, tester.ExplorerNode.Network);
|
.PaymentLink, tester.ExplorerNode.Network);
|
||||||
var halfPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)/2m));
|
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 =>
|
invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model =>
|
||||||
PaymentMethodId.Parse(model.PaymentMethod) ==
|
PaymentMethodId.Parse(model.PaymentMethodId) ==
|
||||||
new PaymentMethodId("BTC", BitcoinPaymentType.Instance))
|
PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||||
.PaymentLink, tester.ExplorerNode.Network);
|
.PaymentLink, tester.ExplorerNode.Network);
|
||||||
var remainingPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)));
|
var remainingPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)));
|
||||||
|
|
||||||
|
@ -2028,8 +2026,8 @@ namespace BTCPayServer.Tests
|
||||||
Currency = "BTC",
|
Currency = "BTC",
|
||||||
});
|
});
|
||||||
invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model =>
|
invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model =>
|
||||||
PaymentMethodId.Parse(model.PaymentMethod) ==
|
PaymentMethodId.Parse(model.PaymentMethodId) ==
|
||||||
new PaymentMethodId("BTC", BitcoinPaymentType.Instance))
|
PaymentTypes.CHAIN.GetPaymentMethodId("BTC"))
|
||||||
.PaymentLink, tester.ExplorerNode.Network);
|
.PaymentLink, tester.ExplorerNode.Network);
|
||||||
halfPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)/2m));
|
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();
|
var ctx = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||||
Assert.Equal(0, invoice.CryptoInfo[0].TxCount);
|
Assert.Equal(0, invoice.CryptoInfo[0].TxCount);
|
||||||
Assert.True(invoice.MinerFees.ContainsKey("BTC"));
|
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(() =>
|
TestUtils.Eventually(() =>
|
||||||
{
|
{
|
||||||
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
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
|
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(() =>
|
TestUtils.Eventually(() =>
|
||||||
{
|
{
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||||
|
@ -2268,7 +2258,7 @@ namespace BTCPayServer.Tests
|
||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
{
|
{
|
||||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
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(Money.Zero, localInvoice.BtcDue);
|
||||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||||
});
|
});
|
||||||
|
@ -2289,7 +2279,7 @@ namespace BTCPayServer.Tests
|
||||||
c =>
|
c =>
|
||||||
{
|
{
|
||||||
Assert.False(c.AfterExpiration);
|
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.NotNull(c.Payment);
|
||||||
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
||||||
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
||||||
|
@ -2299,7 +2289,7 @@ namespace BTCPayServer.Tests
|
||||||
c =>
|
c =>
|
||||||
{
|
{
|
||||||
Assert.False(c.AfterExpiration);
|
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.NotNull(c.Payment);
|
||||||
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
||||||
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
||||||
|
@ -2539,7 +2529,9 @@ namespace BTCPayServer.Tests
|
||||||
await RestartMigration(tester);
|
await RestartMigration(tester);
|
||||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||||
Assert.True(string.IsNullOrEmpty(store.DerivationStrategy));
|
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.AccountDerivation.ToString());
|
||||||
Assert.Equal(derivation, v.AccountOriginal.ToString());
|
Assert.Equal(derivation, v.AccountOriginal.ToString());
|
||||||
Assert.Equal(xpub, v.SigningKey.ToString());
|
Assert.Equal(xpub, v.SigningKey.ToString());
|
||||||
|
@ -2547,13 +2539,26 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
await acc.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning, true);
|
await acc.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning, true);
|
||||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
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());
|
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);
|
await RestartMigration(tester);
|
||||||
|
|
||||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
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.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
|
// Test if legacy lightning charge settings are converted to LightningConnectionString
|
||||||
store.DerivationStrategies = new JObject()
|
store.DerivationStrategies = new JObject()
|
||||||
|
@ -2569,9 +2574,8 @@ namespace BTCPayServer.Tests
|
||||||
}.ToString();
|
}.ToString();
|
||||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||||
await RestartMigration(tester);
|
await RestartMigration(tester);
|
||||||
|
|
||||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
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());
|
Assert.NotNull(lnMethod.GetExternalLightningUrl());
|
||||||
|
|
||||||
var url = lnMethod.GetExternalLightningUrl();
|
var url = lnMethod.GetExternalLightningUrl();
|
||||||
|
@ -2596,8 +2600,23 @@ namespace BTCPayServer.Tests
|
||||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||||
await RestartMigration(tester);
|
await RestartMigration(tester);
|
||||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
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);
|
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)]
|
[Fact(Timeout = LongRunningTestTimeout)]
|
||||||
|
@ -2725,7 +2744,7 @@ namespace BTCPayServer.Tests
|
||||||
serializer.ToString(new Dictionary<string, string>()
|
serializer.ToString(new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToString(),
|
PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(),
|
||||||
new KeyPath("44'/0'/0'").ToString()
|
new KeyPath("44'/0'/0'").ToString()
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
|
@ -2756,13 +2775,33 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Empty(blob.AdditionalData);
|
Assert.Empty(blob.AdditionalData);
|
||||||
Assert.Single(blob.PaymentMethodCriteria);
|
Assert.Single(blob.PaymentMethodCriteria);
|
||||||
Assert.Contains(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");
|
criteria.Above && criteria.Value.Value == 5m && criteria.Value.Currency == "USD");
|
||||||
Assert.Equal(NetworkFeeMode.Never, blob.NetworkFeeMode);
|
Assert.Equal(NetworkFeeMode.Never, blob.NetworkFeeMode);
|
||||||
Assert.Contains(store.GetSupportedPaymentMethods(tester.NetworkProvider), method =>
|
var handlers = tester.PayTester.GetService<PaymentMethodHandlerDictionary>();
|
||||||
method is DerivationSchemeSettings dss &&
|
Assert.Contains(store.GetPaymentMethodConfigs(handlers), method =>
|
||||||
method.PaymentId == new PaymentMethodId("BTC", BitcoinPaymentType.Instance) &&
|
method.Value is DerivationSchemeSettings dss &&
|
||||||
dss.AccountKeyPath == new KeyPath("44'/0'/0'"));
|
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)
|
private static async Task RestartMigration(ServerTester tester)
|
||||||
|
@ -3062,7 +3101,7 @@ namespace BTCPayServer.Tests
|
||||||
// 1 payment on chain
|
// 1 payment on chain
|
||||||
Assert.Equal(4, report.Data.Count);
|
Assert.Equal(4, report.Data.Count);
|
||||||
var lnAddressIndex = report.GetIndex("LightningAddress");
|
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);
|
Assert.Contains(report.Data, d => d[lnAddressIndex]?.Value<string>()?.Contains(acc.LNAddress) is true);
|
||||||
var paymentTypes = report.Data
|
var paymentTypes = report.Data
|
||||||
.GroupBy(d => d[paymentTypeIndex].Value<string>())
|
.GroupBy(d => d[paymentTypeIndex].Value<string>())
|
||||||
|
|
|
@ -216,6 +216,5 @@
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</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_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>
|
||||||
<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>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
@using BTCPayServer.Payments
|
||||||
@using BTCPayServer.Services.Invoices
|
@using BTCPayServer.Services.Invoices
|
||||||
@using BTCPayServer.Abstractions.Extensions
|
@using BTCPayServer.Abstractions.Extensions
|
||||||
@model BTCPayServer.Components.InvoiceStatus.InvoiceStatusViewModel
|
@model BTCPayServer.Components.InvoiceStatus.InvoiceStatusViewModel
|
||||||
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
|
@inject Dictionary<PaymentMethodId, IPaymentModelExtension> Extensions
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var state = Model.State.ToString();
|
var state = Model.State.ToString();
|
||||||
|
@ -41,16 +42,17 @@
|
||||||
</div>
|
</div>
|
||||||
@if (Model.Payments != null)
|
@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 extension = Extensions.TryGetValue(paymentMethodId, out var e) ? e : null;
|
||||||
var badge = paymentMethodId.PaymentType.GetBadge();
|
var image = extension?.Image;
|
||||||
|
var badge = extension?.Badge;
|
||||||
if (!string.IsNullOrEmpty(image) || !string.IsNullOrEmpty(badge))
|
if (!string.IsNullOrEmpty(image) || !string.IsNullOrEmpty(badge))
|
||||||
{
|
{
|
||||||
<span class="d-inline-flex align-items-center gap-1">
|
<span class="d-inline-flex align-items-center gap-1">
|
||||||
@if (!string.IsNullOrEmpty(image))
|
@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))
|
@if (!string.IsNullOrEmpty(badge))
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
@inject PoliciesSettings PoliciesSettings
|
@inject PoliciesSettings PoliciesSettings
|
||||||
@inject ThemeSettings Theme
|
@inject ThemeSettings Theme
|
||||||
@inject PluginService PluginService
|
@inject PluginService PluginService
|
||||||
|
@inject PrettyNameProvider PrettyName
|
||||||
|
|
||||||
@model BTCPayServer.Components.MainNav.MainNavViewModel
|
@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}")">
|
<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 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>
|
</a>
|
||||||
}
|
}
|
||||||
else
|
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}")">
|
<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 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>
|
</a>
|
||||||
}
|
}
|
||||||
</li>
|
</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}")">
|
<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 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>
|
</a>
|
||||||
}
|
}
|
||||||
else
|
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}")">
|
<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 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>
|
</a>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -30,6 +31,7 @@ public class StoreLightningBalance : ViewComponent
|
||||||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||||
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
|
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
|
||||||
public StoreLightningBalance(
|
public StoreLightningBalance(
|
||||||
StoreRepository storeRepo,
|
StoreRepository storeRepo,
|
||||||
|
@ -38,8 +40,9 @@ public class StoreLightningBalance : ViewComponent
|
||||||
BTCPayServerOptions btcpayServerOptions,
|
BTCPayServerOptions btcpayServerOptions,
|
||||||
LightningClientFactoryService lightningClientFactory,
|
LightningClientFactoryService lightningClientFactory,
|
||||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||||
IOptions<ExternalServicesOptions> externalServiceOptions,
|
IOptions<ExternalServicesOptions> externalServiceOptions,
|
||||||
IAuthorizationService authorizationService)
|
IAuthorizationService authorizationService,
|
||||||
|
PaymentMethodHandlerDictionary handlers)
|
||||||
{
|
{
|
||||||
_storeRepo = storeRepo;
|
_storeRepo = storeRepo;
|
||||||
_currencies = currencies;
|
_currencies = currencies;
|
||||||
|
@ -47,6 +50,7 @@ public class StoreLightningBalance : ViewComponent
|
||||||
_btcpayServerOptions = btcpayServerOptions;
|
_btcpayServerOptions = btcpayServerOptions;
|
||||||
_externalServiceOptions = externalServiceOptions;
|
_externalServiceOptions = externalServiceOptions;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
|
_handlers = handlers;
|
||||||
_lightningClientFactory = lightningClientFactory;
|
_lightningClientFactory = lightningClientFactory;
|
||||||
_lightningNetworkOptions = lightningNetworkOptions;
|
_lightningNetworkOptions = lightningNetworkOptions;
|
||||||
}
|
}
|
||||||
|
@ -101,10 +105,8 @@ public class StoreLightningBalance : ViewComponent
|
||||||
private async Task<ILightningClient> GetLightningClient(StoreData store, string cryptoCode )
|
private async Task<ILightningClient> GetLightningClient(StoreData store, string cryptoCode )
|
||||||
{
|
{
|
||||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
var network = _networkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
var id = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||||
var existing = store.GetSupportedPaymentMethods(_networkProvider)
|
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id, _handlers);
|
||||||
.OfType<LightningSupportedPaymentMethod>()
|
|
||||||
.FirstOrDefault(d => d.PaymentId == id);
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,9 @@ using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Labels;
|
using BTCPayServer.Services.Labels;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
|
@ -27,21 +29,20 @@ public class StoreRecentTransactions : ViewComponent
|
||||||
private readonly BTCPayWalletProvider _walletProvider;
|
private readonly BTCPayWalletProvider _walletProvider;
|
||||||
private readonly WalletRepository _walletRepository;
|
private readonly WalletRepository _walletRepository;
|
||||||
private readonly LabelService _labelService;
|
private readonly LabelService _labelService;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||||
|
|
||||||
public BTCPayNetworkProvider NetworkProvider { get; }
|
|
||||||
|
|
||||||
public StoreRecentTransactions(
|
public StoreRecentTransactions(
|
||||||
BTCPayNetworkProvider networkProvider,
|
|
||||||
BTCPayWalletProvider walletProvider,
|
BTCPayWalletProvider walletProvider,
|
||||||
WalletRepository walletRepository,
|
WalletRepository walletRepository,
|
||||||
LabelService labelService,
|
LabelService labelService,
|
||||||
|
PaymentMethodHandlerDictionary handlers,
|
||||||
TransactionLinkProviders transactionLinkProviders)
|
TransactionLinkProviders transactionLinkProviders)
|
||||||
{
|
{
|
||||||
NetworkProvider = networkProvider;
|
|
||||||
_walletProvider = walletProvider;
|
_walletProvider = walletProvider;
|
||||||
_walletRepository = walletRepository;
|
_walletRepository = walletRepository;
|
||||||
_labelService = labelService;
|
_labelService = labelService;
|
||||||
|
_handlers = handlers;
|
||||||
_transactionLinkProviders = transactionLinkProviders;
|
_transactionLinkProviders = transactionLinkProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,15 +58,16 @@ public class StoreRecentTransactions : ViewComponent
|
||||||
if (vm.InitialRendering)
|
if (vm.InitialRendering)
|
||||||
return View(vm);
|
return View(vm);
|
||||||
|
|
||||||
var derivationSettings = vm.Store.GetDerivationSchemeSettings(NetworkProvider, vm.CryptoCode);
|
var derivationSettings = vm.Store.GetDerivationSchemeSettings(_handlers, vm.CryptoCode);
|
||||||
var transactions = new List<StoreRecentTransactionViewModel>();
|
var transactions = new List<StoreRecentTransactionViewModel>();
|
||||||
if (derivationSettings?.AccountDerivation is not null)
|
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 wallet = _walletProvider.GetWallet(network);
|
||||||
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0), cancellationToken: this.HttpContext.RequestAborted);
|
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 walletTransactionsInfo = await _walletRepository.GetWalletTransactionsInfo(vm.WalletId, allTransactions.Select(t => t.TransactionId.ToString()).ToArray());
|
||||||
var pmi = new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike);
|
|
||||||
transactions = allTransactions
|
transactions = allTransactions
|
||||||
.Select(tx =>
|
.Select(tx =>
|
||||||
{
|
{
|
||||||
|
@ -78,7 +80,7 @@ public class StoreRecentTransactions : ViewComponent
|
||||||
Balance = tx.BalanceChange.ShowMoney(network),
|
Balance = tx.BalanceChange.ShowMoney(network),
|
||||||
Currency = vm.CryptoCode,
|
Currency = vm.CryptoCode,
|
||||||
IsConfirmed = tx.Confirmations != 0,
|
IsConfirmed = tx.Confirmations != 0,
|
||||||
Link = _transactionLinkProviders.GetTransactionLink(pmi, tx.TransactionId.ToString()),
|
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, tx.TransactionId.ToString()),
|
||||||
Timestamp = tx.SeenAt,
|
Timestamp = tx.SeenAt,
|
||||||
Labels = labels
|
Labels = labels
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,8 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -12,17 +14,14 @@ namespace BTCPayServer.Components.StoreSelector
|
||||||
public class StoreSelector : ViewComponent
|
public class StoreSelector : ViewComponent
|
||||||
{
|
{
|
||||||
private readonly StoreRepository _storeRepo;
|
private readonly StoreRepository _storeRepo;
|
||||||
private readonly BTCPayNetworkProvider _networkProvider;
|
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
|
||||||
public StoreSelector(
|
public StoreSelector(
|
||||||
StoreRepository storeRepo,
|
StoreRepository storeRepo,
|
||||||
BTCPayNetworkProvider networkProvider,
|
|
||||||
UserManager<ApplicationUser> userManager)
|
UserManager<ApplicationUser> userManager)
|
||||||
{
|
{
|
||||||
_storeRepo = storeRepo;
|
_storeRepo = storeRepo;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_networkProvider = networkProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync()
|
public async Task<IViewComponentResult> InvokeAsync()
|
||||||
|
@ -34,21 +33,12 @@ namespace BTCPayServer.Components.StoreSelector
|
||||||
var options = stores
|
var options = stores
|
||||||
.Where(store => !store.Archived)
|
.Where(store => !store.Archived)
|
||||||
.Select(store =>
|
.Select(store =>
|
||||||
|
new StoreSelectorOption
|
||||||
{
|
{
|
||||||
var cryptoCode = store
|
Text = store.StoreName,
|
||||||
.GetSupportedPaymentMethods(_networkProvider)
|
Value = store.Id,
|
||||||
.OfType<DerivationSchemeSettings>()
|
Selected = store.Id == currentStore?.Id,
|
||||||
.FirstOrDefault()?
|
Store = store
|
||||||
.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
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
.OrderBy(s => s.Text)
|
.OrderBy(s => s.Text)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
|
@ -17,7 +17,6 @@ namespace BTCPayServer.Components.StoreSelector
|
||||||
public bool Selected { get; set; }
|
public bool Selected { get; set; }
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
public WalletId WalletId { get; set; }
|
|
||||||
public StoreData Store { get; set; }
|
public StoreData Store { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
{
|
{
|
||||||
<div class="ct-chart"></div>
|
<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>
|
<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>.
|
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 System.Threading.Tasks;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
|
@ -29,19 +30,22 @@ public class StoreWalletBalance : ViewComponent
|
||||||
private readonly WalletHistogramService _walletHistogramService;
|
private readonly WalletHistogramService _walletHistogramService;
|
||||||
private readonly BTCPayWalletProvider _walletProvider;
|
private readonly BTCPayWalletProvider _walletProvider;
|
||||||
private readonly BTCPayNetworkProvider _networkProvider;
|
private readonly BTCPayNetworkProvider _networkProvider;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
|
||||||
public StoreWalletBalance(
|
public StoreWalletBalance(
|
||||||
StoreRepository storeRepo,
|
StoreRepository storeRepo,
|
||||||
CurrencyNameTable currencies,
|
CurrencyNameTable currencies,
|
||||||
WalletHistogramService walletHistogramService,
|
WalletHistogramService walletHistogramService,
|
||||||
BTCPayWalletProvider walletProvider,
|
BTCPayWalletProvider walletProvider,
|
||||||
BTCPayNetworkProvider networkProvider)
|
BTCPayNetworkProvider networkProvider,
|
||||||
|
PaymentMethodHandlerDictionary handlers)
|
||||||
{
|
{
|
||||||
_storeRepo = storeRepo;
|
_storeRepo = storeRepo;
|
||||||
_currencies = currencies;
|
_currencies = currencies;
|
||||||
_walletProvider = walletProvider;
|
_walletProvider = walletProvider;
|
||||||
_walletHistogramService = walletHistogramService;
|
|
||||||
_networkProvider = networkProvider;
|
_networkProvider = networkProvider;
|
||||||
|
_walletHistogramService = walletHistogramService;
|
||||||
|
_handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
public async Task<IViewComponentResult> InvokeAsync(StoreData store)
|
||||||
|
@ -71,11 +75,12 @@ public class StoreWalletBalance : ViewComponent
|
||||||
{
|
{
|
||||||
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(3));
|
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(3));
|
||||||
var wallet = _walletProvider.GetWallet(_networkProvider.DefaultNetwork);
|
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)
|
if (derivation is not null)
|
||||||
{
|
{
|
||||||
var balance = await wallet.GetBalance(derivation.AccountDerivation, cts.Token);
|
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
|
public class WalletNav : ViewComponent
|
||||||
{
|
{
|
||||||
private readonly BTCPayWalletProvider _walletProvider;
|
private readonly BTCPayWalletProvider _walletProvider;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly UIWalletsController _walletsController;
|
private readonly UIWalletsController _walletsController;
|
||||||
private readonly CurrencyNameTable _currencies;
|
private readonly CurrencyNameTable _currencies;
|
||||||
private readonly BTCPayNetworkProvider _networkProvider;
|
private readonly BTCPayNetworkProvider _networkProvider;
|
||||||
|
@ -33,12 +34,14 @@ namespace BTCPayServer.Components.WalletNav
|
||||||
|
|
||||||
public WalletNav(
|
public WalletNav(
|
||||||
BTCPayWalletProvider walletProvider,
|
BTCPayWalletProvider walletProvider,
|
||||||
|
PaymentMethodHandlerDictionary handlers,
|
||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
UIWalletsController walletsController,
|
UIWalletsController walletsController,
|
||||||
CurrencyNameTable currencies,
|
CurrencyNameTable currencies,
|
||||||
RateFetcher rateFetcher)
|
RateFetcher rateFetcher)
|
||||||
{
|
{
|
||||||
_walletProvider = walletProvider;
|
_walletProvider = walletProvider;
|
||||||
|
_handlers = handlers;
|
||||||
_networkProvider = networkProvider;
|
_networkProvider = networkProvider;
|
||||||
_walletsController = walletsController;
|
_walletsController = walletsController;
|
||||||
_currencies = currencies;
|
_currencies = currencies;
|
||||||
|
@ -51,7 +54,7 @@ namespace BTCPayServer.Components.WalletNav
|
||||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
var network = _networkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||||
var wallet = _walletProvider.GetWallet(network);
|
var wallet = _walletProvider.GetWallet(network);
|
||||||
var defaultCurrency = store.GetStoreBlob().DefaultCurrency;
|
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
|
var balance = await wallet.GetBalance(derivation?.AccountDerivation) switch
|
||||||
{
|
{
|
||||||
{ Available: null, Total: var total } => total,
|
{ Available: null, Total: var total } => total,
|
||||||
|
|
|
@ -26,12 +26,15 @@ namespace BTCPayServer.Controllers
|
||||||
public class BitpayInvoiceController : Controller
|
public class BitpayInvoiceController : Controller
|
||||||
{
|
{
|
||||||
private readonly UIInvoiceController _InvoiceController;
|
private readonly UIInvoiceController _InvoiceController;
|
||||||
|
private readonly Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension> _bitpayExtensions;
|
||||||
private readonly InvoiceRepository _InvoiceRepository;
|
private readonly InvoiceRepository _InvoiceRepository;
|
||||||
|
|
||||||
public BitpayInvoiceController(UIInvoiceController invoiceController,
|
public BitpayInvoiceController(UIInvoiceController invoiceController,
|
||||||
|
Dictionary<PaymentMethodId, IPaymentMethodBitpayAPIExtension> bitpayExtensions,
|
||||||
InvoiceRepository invoiceRepository)
|
InvoiceRepository invoiceRepository)
|
||||||
{
|
{
|
||||||
_InvoiceController = invoiceController;
|
_InvoiceController = invoiceController;
|
||||||
|
_bitpayExtensions = bitpayExtensions;
|
||||||
_InvoiceRepository = invoiceRepository;
|
_InvoiceRepository = invoiceRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +59,7 @@ namespace BTCPayServer.Controllers
|
||||||
})).FirstOrDefault();
|
})).FirstOrDefault();
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
throw new BitpayHttpException(404, "Object not found");
|
throw new BitpayHttpException(404, "Object not found");
|
||||||
return new DataWrapper<InvoiceResponse>(invoice.EntityToDTO());
|
return new DataWrapper<InvoiceResponse>(invoice.EntityToDTO(_bitpayExtensions, Url));
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("invoices")]
|
[Route("invoices")]
|
||||||
|
@ -90,7 +93,7 @@ namespace BTCPayServer.Controllers
|
||||||
};
|
};
|
||||||
|
|
||||||
var entities = (await _InvoiceRepository.GetInvoices(query))
|
var entities = (await _InvoiceRepository.GetInvoices(query))
|
||||||
.Select((o) => o.EntityToDTO()).ToArray();
|
.Select((o) => o.EntityToDTO(_bitpayExtensions, Url)).ToArray();
|
||||||
|
|
||||||
return Json(DataWrapper.Create(entities));
|
return Json(DataWrapper.Create(entities));
|
||||||
}
|
}
|
||||||
|
@ -100,7 +103,7 @@ namespace BTCPayServer.Controllers
|
||||||
CancellationToken cancellationToken = default, Action<InvoiceEntity> entityManipulator = null)
|
CancellationToken cancellationToken = default, Action<InvoiceEntity> entityManipulator = null)
|
||||||
{
|
{
|
||||||
var entity = await CreateInvoiceCoreRaw(invoice, store, serverUrl, additionalTags, cancellationToken, entityManipulator);
|
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" };
|
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +181,10 @@ namespace BTCPayServer.Controllers
|
||||||
excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p));
|
excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p));
|
||||||
}
|
}
|
||||||
entity.PaymentTolerance = storeBlob.PaymentTolerance;
|
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;
|
entity.RequiresRefundEmail = invoice.RequiresRefundEmail;
|
||||||
|
|
||||||
return await _InvoiceController.CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken, entityManipulator);
|
return await _InvoiceController.CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken, entityManipulator);
|
||||||
|
|
|
@ -9,9 +9,12 @@ using BTCPayServer.Abstractions.Constants;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Security.Bitpay;
|
using BTCPayServer.Security.Bitpay;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -29,7 +32,9 @@ namespace BTCPayServer.Controllers
|
||||||
readonly RateFetcher _rateProviderFactory;
|
readonly RateFetcher _rateProviderFactory;
|
||||||
readonly BTCPayNetworkProvider _networkProvider;
|
readonly BTCPayNetworkProvider _networkProvider;
|
||||||
readonly CurrencyNameTable _currencyNameTable;
|
readonly CurrencyNameTable _currencyNameTable;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
readonly StoreRepository _storeRepo;
|
readonly StoreRepository _storeRepo;
|
||||||
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
|
|
||||||
private StoreData CurrentStore => HttpContext.GetStoreData();
|
private StoreData CurrentStore => HttpContext.GetStoreData();
|
||||||
|
|
||||||
|
@ -37,12 +42,16 @@ namespace BTCPayServer.Controllers
|
||||||
RateFetcher rateProviderFactory,
|
RateFetcher rateProviderFactory,
|
||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
StoreRepository storeRepo,
|
StoreRepository storeRepo,
|
||||||
CurrencyNameTable currencyNameTable)
|
InvoiceRepository invoiceRepository,
|
||||||
|
CurrencyNameTable currencyNameTable,
|
||||||
|
PaymentMethodHandlerDictionary handlers)
|
||||||
{
|
{
|
||||||
_rateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
|
_rateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
|
||||||
_networkProvider = networkProvider;
|
_networkProvider = networkProvider;
|
||||||
_storeRepo = storeRepo;
|
_storeRepo = storeRepo;
|
||||||
|
_invoiceRepository = invoiceRepository;
|
||||||
_currencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
_currencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||||
|
_handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("rates/{baseCurrency}")]
|
[Route("rates/{baseCurrency}")]
|
||||||
|
@ -50,11 +59,17 @@ namespace BTCPayServer.Controllers
|
||||||
[BitpayAPIConstraint]
|
[BitpayAPIConstraint]
|
||||||
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, string cryptoCode = null, CancellationToken cancellationToken = default)
|
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, string cryptoCode = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var supportedMethods = CurrentStore.GetSupportedPaymentMethods(_networkProvider);
|
var inv = _invoiceRepository.CreateNewInvoice(CurrentStore.Id);
|
||||||
|
inv.Currency = baseCurrency;
|
||||||
var currencyCodes = supportedMethods.Where(method => !string.IsNullOrEmpty(method.PaymentId.CryptoCode))
|
var ctx = new InvoiceCreationContext(CurrentStore, CurrentStore.GetStoreBlob(), inv, new Logging.InvoiceLogs(), _handlers, null);
|
||||||
.Select(method => method.PaymentId.CryptoCode).Distinct();
|
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 currencypairs = BuildCurrencyPairs(currencyCodes, baseCurrency);
|
||||||
|
|
||||||
var result = await GetRates2(currencypairs, null, cryptoCode, cancellationToken);
|
var result = await GetRates2(currencypairs, null, cryptoCode, cancellationToken);
|
||||||
|
|
|
@ -11,6 +11,7 @@ using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Security.Greenfield;
|
using BTCPayServer.Security.Greenfield;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
@ -21,7 +22,9 @@ using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
|
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
|
||||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||||
|
|
||||||
|
@ -42,6 +45,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
private readonly InvoiceActivator _invoiceActivator;
|
private readonly InvoiceActivator _invoiceActivator;
|
||||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
private readonly Dictionary<PaymentMethodId, IPaymentLinkExtension> _paymentLinkExtensions;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
|
||||||
public LanguageService LanguageService { get; }
|
public LanguageService LanguageService { get; }
|
||||||
|
|
||||||
|
@ -51,7 +56,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
InvoiceActivator invoiceActivator,
|
InvoiceActivator invoiceActivator,
|
||||||
PullPaymentHostedService pullPaymentService,
|
PullPaymentHostedService pullPaymentService,
|
||||||
ApplicationDbContextFactory dbContextFactory,
|
ApplicationDbContextFactory dbContextFactory,
|
||||||
IAuthorizationService authorizationService)
|
IAuthorizationService authorizationService,
|
||||||
|
Dictionary<PaymentMethodId, IPaymentLinkExtension> paymentLinkExtensions,
|
||||||
|
PaymentMethodHandlerDictionary handlers)
|
||||||
{
|
{
|
||||||
_invoiceController = invoiceController;
|
_invoiceController = invoiceController;
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
|
@ -63,6 +70,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
_pullPaymentService = pullPaymentService;
|
_pullPaymentService = pullPaymentService;
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
|
_paymentLinkExtensions = paymentLinkExtensions;
|
||||||
|
_handlers = handlers;
|
||||||
LanguageService = languageService;
|
LanguageService = languageService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +356,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
|
||||||
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
|
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
|
||||||
{
|
{
|
||||||
await _invoiceActivator.ActivateInvoicePaymentMethod(paymentMethodId, invoice, store);
|
await _invoiceActivator.ActivateInvoicePaymentMethod(invoiceId, paymentMethodId);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
ModelState.AddModelError(nameof(paymentMethod), "Invalid payment method");
|
ModelState.AddModelError(nameof(paymentMethod), "Invalid payment method");
|
||||||
|
@ -384,33 +393,31 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
{
|
{
|
||||||
return this.CreateAPIError("non-refundable", "Cannot refund this invoice");
|
return this.CreateAPIError("non-refundable", "Cannot refund this invoice");
|
||||||
}
|
}
|
||||||
PaymentMethod? invoicePaymentMethod = null;
|
PaymentPrompt? paymentPrompt = null;
|
||||||
PaymentMethodId? paymentMethodId = null;
|
PaymentMethodId? paymentMethodId = null;
|
||||||
if (request.PaymentMethod is not null && PaymentMethodId.TryParse(request.PaymentMethod, out paymentMethodId))
|
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");
|
ModelState.AddModelError(nameof(request.PaymentMethod), "Please select one of the payment methods which were available for the original invoice");
|
||||||
}
|
}
|
||||||
if (request.RefundVariant is null)
|
if (request.RefundVariant is null)
|
||||||
ModelState.AddModelError(nameof(request.RefundVariant), "`refundVariant` is mandatory");
|
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);
|
return this.CreateValidationError(ModelState);
|
||||||
|
|
||||||
var accounting = invoicePaymentMethod.Calculate();
|
var accounting = paymentPrompt.Calculate();
|
||||||
var cryptoPaid = accounting.Paid;
|
var cryptoPaid = accounting.Paid;
|
||||||
var cdCurrency = _currencyNameTable.GetCurrencyData(invoice.Currency, true);
|
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(
|
var rateResult = await _rateProvider.FetchRate(
|
||||||
new CurrencyPair(paymentMethodId.CryptoCode, invoice.Currency),
|
new CurrencyPair(paymentPrompt.Currency, invoice.Currency),
|
||||||
store.GetStoreBlob().GetRateRules(_networkProvider),
|
store.GetStoreBlob().GetRateRules(_networkProvider),
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
var cryptoCode = invoicePaymentMethod.GetId().CryptoCode;
|
var paidAmount = cryptoPaid.RoundToSignificant(paymentPrompt.Divisibility);
|
||||||
var paymentMethodDivisibility = _currencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
|
|
||||||
var paidAmount = cryptoPaid.RoundToSignificant(paymentMethodDivisibility);
|
|
||||||
var createPullPayment = new CreatePullPayment
|
var createPullPayment = new CreatePullPayment
|
||||||
{
|
{
|
||||||
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration,
|
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration,
|
||||||
|
@ -436,17 +443,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
return this.CreateValidationError(ModelState);
|
return this.CreateValidationError(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
var appliedDivisibility = paymentMethodDivisibility;
|
var appliedDivisibility = paymentPrompt.Divisibility;
|
||||||
switch (request.RefundVariant)
|
switch (request.RefundVariant)
|
||||||
{
|
{
|
||||||
case RefundVariant.RateThen:
|
case RefundVariant.RateThen:
|
||||||
createPullPayment.Currency = cryptoCode;
|
createPullPayment.Currency = paymentPrompt.Currency;
|
||||||
createPullPayment.Amount = paidAmount;
|
createPullPayment.Amount = paidAmount;
|
||||||
createPullPayment.AutoApproveClaims = true;
|
createPullPayment.AutoApproveClaims = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RefundVariant.CurrentRate:
|
case RefundVariant.CurrentRate:
|
||||||
createPullPayment.Currency = cryptoCode;
|
createPullPayment.Currency = paymentPrompt.Currency;
|
||||||
createPullPayment.Amount = Math.Round(paidCurrency / rateResult.BidAsk.Bid, appliedDivisibility);
|
createPullPayment.Amount = Math.Round(paidCurrency / rateResult.BidAsk.Bid, appliedDivisibility);
|
||||||
createPullPayment.AutoApproveClaims = true;
|
createPullPayment.AutoApproveClaims = true;
|
||||||
break;
|
break;
|
||||||
|
@ -469,7 +476,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
}
|
}
|
||||||
|
|
||||||
var dueAmount = accounting.TotalDue;
|
var dueAmount = accounting.TotalDue;
|
||||||
createPullPayment.Currency = cryptoCode;
|
createPullPayment.Currency = paymentPrompt.Currency;
|
||||||
createPullPayment.Amount = Math.Round(paidAmount - dueAmount, appliedDivisibility);
|
createPullPayment.Amount = Math.Round(paidAmount - dueAmount, appliedDivisibility);
|
||||||
createPullPayment.AutoApproveClaims = true;
|
createPullPayment.AutoApproveClaims = true;
|
||||||
break;
|
break;
|
||||||
|
@ -501,7 +508,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
|
||||||
createPullPayment.Currency = request.CustomCurrency;
|
createPullPayment.Currency = request.CustomCurrency;
|
||||||
createPullPayment.Amount = request.CustomAmount.Value;
|
createPullPayment.Amount = request.CustomAmount.Value;
|
||||||
createPullPayment.AutoApproveClaims = paymentMethodId.CryptoCode == request.CustomCurrency;
|
createPullPayment.AutoApproveClaims = paymentPrompt.Currency == request.CustomCurrency;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -569,49 +576,52 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
|
||||||
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity, bool includeAccountedPaymentOnly)
|
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity, bool includeAccountedPaymentOnly)
|
||||||
{
|
{
|
||||||
return entity.GetPaymentMethods().Select(
|
return entity.GetPaymentPrompts().Select(
|
||||||
method =>
|
prompt =>
|
||||||
{
|
{
|
||||||
var accounting = method.Calculate();
|
_handlers.TryGetValue(prompt.PaymentMethodId, out var handler);
|
||||||
var details = method.GetPaymentMethodDetails();
|
var accounting = prompt.Currency is not null ? prompt.Calculate() : null;
|
||||||
var payments = method.ParentEntity.GetPayments(includeAccountedPaymentOnly).Where(paymentEntity =>
|
var payments = prompt.ParentEntity.GetPayments(includeAccountedPaymentOnly).Where(paymentEntity =>
|
||||||
paymentEntity.GetPaymentMethodId() == method.GetId());
|
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
|
return new InvoicePaymentMethodDataModel
|
||||||
{
|
{
|
||||||
Activated = details.Activated,
|
Activated = prompt.Activated,
|
||||||
PaymentMethod = method.GetId().ToStringNormalized(),
|
PaymentMethodId = prompt.PaymentMethodId.ToString(),
|
||||||
CryptoCode = method.GetId().CryptoCode,
|
Currency = prompt.Currency,
|
||||||
Destination = details.GetPaymentDestination(),
|
Destination = prompt.Destination,
|
||||||
Rate = method.Rate,
|
Rate = prompt.Currency is not null ? prompt.Rate : 0m,
|
||||||
Due = accounting.DueUncapped,
|
Due = accounting?.DueUncapped ?? 0m,
|
||||||
TotalPaid = accounting.Paid,
|
TotalPaid = accounting?.Paid ?? 0m,
|
||||||
PaymentMethodPaid = accounting.CryptoPaid,
|
PaymentMethodPaid = accounting?.PaymentMethodPaid ?? 0m,
|
||||||
Amount = accounting.TotalDue,
|
Amount = accounting?.TotalDue ?? 0m,
|
||||||
NetworkFee = accounting.NetworkFee,
|
PaymentMethodFee = accounting?.PaymentMethodFee ?? 0m,
|
||||||
PaymentLink =
|
PaymentLink = (prompt.Activated ? paymentLinkExtension?.GetPaymentLink(prompt, Url) : null) ?? string.Empty,
|
||||||
method.GetId().PaymentType.GetPaymentLink(method.Network, entity, details, accounting.Due,
|
|
||||||
Request.GetAbsoluteRoot()),
|
|
||||||
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
|
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
|
||||||
AdditionalData = details.GetAdditionalData()
|
AdditionalData = prompt.Details
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InvoicePaymentMethodDataModel.Payment ToPaymentModel(InvoiceEntity entity, PaymentEntity paymentEntity)
|
public static InvoicePaymentMethodDataModel.Payment ToPaymentModel(InvoiceEntity entity, PaymentEntity paymentEntity)
|
||||||
{
|
{
|
||||||
var data = paymentEntity.GetCryptoPaymentData();
|
|
||||||
return new InvoicePaymentMethodDataModel.Payment()
|
return new InvoicePaymentMethodDataModel.Payment()
|
||||||
{
|
{
|
||||||
Destination = data.GetDestination(),
|
Destination = paymentEntity.Destination,
|
||||||
Id = data.GetPaymentId(),
|
Id = paymentEntity.Id,
|
||||||
Status = !paymentEntity.Accounted
|
Status = paymentEntity.Status switch
|
||||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid
|
{
|
||||||
: data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) || data.PaymentCompleted(paymentEntity)
|
PaymentStatus.Processing => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
||||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled
|
PaymentStatus.Settled => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled,
|
||||||
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
PaymentStatus.Unaccounted => InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid,
|
||||||
Fee = paymentEntity.NetworkFee,
|
_ => throw new NotSupportedException(paymentEntity.Status.ToString())
|
||||||
Value = data.GetValue(),
|
},
|
||||||
|
Fee = paymentEntity.PaymentMethodFee,
|
||||||
|
Value = paymentEntity.Value,
|
||||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -656,8 +666,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
Monitoring = entity.MonitoringExpiration - entity.ExpirationTime,
|
Monitoring = entity.MonitoringExpiration - entity.ExpirationTime,
|
||||||
PaymentTolerance = entity.PaymentTolerance,
|
PaymentTolerance = entity.PaymentTolerance,
|
||||||
PaymentMethods =
|
PaymentMethods =
|
||||||
entity.GetPaymentMethods().Select(method => method.GetId().ToStringNormalized()).ToArray(),
|
entity.GetPaymentPrompts().Select(method => method.PaymentMethodId.ToString()).ToArray(),
|
||||||
DefaultPaymentMethod = entity.DefaultPaymentMethod,
|
DefaultPaymentMethod = entity.DefaultPaymentMethod?.ToString(),
|
||||||
SpeedPolicy = entity.SpeedPolicy,
|
SpeedPolicy = entity.SpeedPolicy,
|
||||||
DefaultLanguage = entity.DefaultLanguage,
|
DefaultLanguage = entity.DefaultLanguage,
|
||||||
RedirectAutomatically = entity.RedirectAutomatically,
|
RedirectAutomatically = entity.RedirectAutomatically,
|
||||||
|
|
|
@ -9,6 +9,7 @@ using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -22,19 +23,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
[EnableCors(CorsPolicies.All)]
|
[EnableCors(CorsPolicies.All)]
|
||||||
public class GreenfieldInternalLightningNodeApiController : GreenfieldLightningNodeApiController
|
public class GreenfieldInternalLightningNodeApiController : GreenfieldLightningNodeApiController
|
||||||
{
|
{
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
||||||
private readonly LightningClientFactoryService _lightningClientFactory;
|
private readonly LightningClientFactoryService _lightningClientFactory;
|
||||||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
|
||||||
public GreenfieldInternalLightningNodeApiController(
|
public GreenfieldInternalLightningNodeApiController(
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider, PoliciesSettings policiesSettings, LightningClientFactoryService lightningClientFactory,
|
PoliciesSettings policiesSettings, LightningClientFactoryService lightningClientFactory,
|
||||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||||
IAuthorizationService authorizationService) : base(
|
IAuthorizationService authorizationService,
|
||||||
btcPayNetworkProvider, policiesSettings, authorizationService)
|
PaymentMethodHandlerDictionary handlers
|
||||||
|
) : base(policiesSettings, authorizationService, handlers)
|
||||||
{
|
{
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
|
||||||
_lightningClientFactory = lightningClientFactory;
|
_lightningClientFactory = lightningClientFactory;
|
||||||
_lightningNetworkOptions = lightningNetworkOptions;
|
_lightningNetworkOptions = lightningNetworkOptions;
|
||||||
|
_handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||||
|
@ -135,7 +137,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
|
||||||
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
|
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
|
||||||
{
|
{
|
||||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
var network = GetNetwork(cryptoCode);
|
||||||
if (network is null)
|
if (network is null)
|
||||||
throw ErrorCryptoCodeNotFound();
|
throw ErrorCryptoCodeNotFound();
|
||||||
if (!_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode,
|
if (!_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode,
|
||||||
|
|
|
@ -12,6 +12,8 @@ using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using ExchangeSharp;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -27,18 +29,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
{
|
{
|
||||||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||||
private readonly LightningClientFactoryService _lightningClientFactory;
|
private readonly LightningClientFactoryService _lightningClientFactory;
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
|
||||||
public GreenfieldStoreLightningNodeApiController(
|
public GreenfieldStoreLightningNodeApiController(
|
||||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||||
LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider,
|
LightningClientFactoryService lightningClientFactory, PaymentMethodHandlerDictionary handlers,
|
||||||
PoliciesSettings policiesSettings,
|
PoliciesSettings policiesSettings,
|
||||||
IAuthorizationService authorizationService) : base(
|
IAuthorizationService authorizationService) : base(policiesSettings, authorizationService, handlers)
|
||||||
btcPayNetworkProvider, policiesSettings, authorizationService)
|
|
||||||
{
|
{
|
||||||
_lightningNetworkOptions = lightningNetworkOptions;
|
_lightningNetworkOptions = lightningNetworkOptions;
|
||||||
_lightningClientFactory = lightningClientFactory;
|
_lightningClientFactory = lightningClientFactory;
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
_handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||||
|
@ -138,22 +139,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
protected override Task<ILightningClient> GetLightningClient(string cryptoCode,
|
protected override Task<ILightningClient> GetLightningClient(string cryptoCode,
|
||||||
bool doingAdminThings)
|
bool doingAdminThings)
|
||||||
{
|
{
|
||||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
if (!_handlers.TryGetValue(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), out var o) ||
|
||||||
if (network == null)
|
o is not LightningLikePaymentHandler handler)
|
||||||
{
|
{
|
||||||
throw ErrorCryptoCodeNotFound();
|
throw ErrorCryptoCodeNotFound();
|
||||||
}
|
}
|
||||||
|
var network = handler.Network;
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
if (store == null)
|
if (store == null)
|
||||||
{
|
{
|
||||||
throw new JsonHttpException(StoreNotFound());
|
throw new JsonHttpException(StoreNotFound());
|
||||||
}
|
}
|
||||||
|
|
||||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
var id = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||||
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id, _handlers);
|
||||||
.OfType<LightningSupportedPaymentMethod>()
|
|
||||||
.FirstOrDefault(d => d.PaymentId == id);
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
throw ErrorLightningNodeNotConfiguredForStore();
|
throw ErrorLightningNodeNotConfiguredForStore();
|
||||||
if (existing.GetExternalLightningUrl() is {} connectionString)
|
if (existing.GetExternalLightningUrl() is {} connectionString)
|
||||||
|
|
|
@ -6,8 +6,11 @@ using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
@ -26,16 +29,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
|
||||||
public abstract class GreenfieldLightningNodeApiController : Controller
|
public abstract class GreenfieldLightningNodeApiController : Controller
|
||||||
{
|
{
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
||||||
private readonly PoliciesSettings _policiesSettings;
|
private readonly PoliciesSettings _policiesSettings;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
protected GreenfieldLightningNodeApiController(BTCPayNetworkProvider btcPayNetworkProvider,
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
|
||||||
|
protected GreenfieldLightningNodeApiController(
|
||||||
PoliciesSettings policiesSettings,
|
PoliciesSettings policiesSettings,
|
||||||
IAuthorizationService authorizationService)
|
IAuthorizationService authorizationService,
|
||||||
|
PaymentMethodHandlerDictionary handlers)
|
||||||
{
|
{
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
|
||||||
_policiesSettings = policiesSettings;
|
_policiesSettings = policiesSettings;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
|
_handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task<IActionResult> GetInfo(string cryptoCode, CancellationToken cancellationToken = default)
|
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)
|
public virtual async Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
var network = GetNetwork(cryptoCode);
|
||||||
BOLT11PaymentRequest bolt11 = null;
|
BOLT11PaymentRequest bolt11 = null;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(lightningInvoice.BOLT11) ||
|
if (string.IsNullOrEmpty(lightningInvoice.BOLT11) ||
|
||||||
|
@ -336,7 +341,11 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
return this.CreateAPIError("generic-error", ex.Message);
|
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()
|
protected JsonHttpException ErrorLightningNodeNotConfiguredForStore()
|
||||||
{
|
{
|
||||||
return new JsonHttpException(this.CreateAPIError(404, "lightning-not-configured", "The lightning node is not set up"));
|
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,
|
Name = factory.Processor,
|
||||||
FriendlyName = factory.FriendlyName,
|
FriendlyName = factory.FriendlyName,
|
||||||
PaymentMethods = factory.GetSupportedPaymentMethods().Select(id => id.ToStringNormalized())
|
PaymentMethods = factory.GetSupportedPaymentMethods().Select(id => id.ToString())
|
||||||
.ToArray()
|
.ToArray()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
public ActionResult ServerInfo()
|
public ActionResult ServerInfo()
|
||||||
{
|
{
|
||||||
var supportedPaymentMethods = _paymentMethodHandlerDictionary
|
var supportedPaymentMethods = _paymentMethodHandlerDictionary
|
||||||
.SelectMany(handler => handler.GetSupportedPaymentMethods().Select(id => id.ToString()))
|
.Select(handler => handler.PaymentMethodId.ToString())
|
||||||
.Distinct();
|
.Distinct();
|
||||||
|
|
||||||
ServerInfoData model = new ServerInfoData2
|
ServerInfoData model = new ServerInfoData2
|
||||||
|
|
|
@ -38,14 +38,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
public async Task<IActionResult> GetStoreLightningAutomatedPayoutProcessors(
|
public async Task<IActionResult> GetStoreLightningAutomatedPayoutProcessors(
|
||||||
string storeId, string? paymentMethod)
|
string storeId, string? paymentMethod)
|
||||||
{
|
{
|
||||||
paymentMethod = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod).ToString() : null;
|
var paymentMethodId = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod) : null;
|
||||||
var configured =
|
var configured =
|
||||||
await _payoutProcessorService.GetProcessors(
|
await _payoutProcessorService.GetProcessors(
|
||||||
new PayoutProcessorService.PayoutProcessorQuery()
|
new PayoutProcessorService.PayoutProcessorQuery()
|
||||||
{
|
{
|
||||||
Stores = new[] { storeId },
|
Stores = new[] { storeId },
|
||||||
Processors = new[] { LightningAutomatedPayoutSenderFactory.ProcessorName },
|
Processors = new[] { LightningAutomatedPayoutSenderFactory.ProcessorName },
|
||||||
PaymentMethods = paymentMethod is null ? null : new[] { paymentMethod }
|
PaymentMethods = paymentMethodId is null ? null : new[] { paymentMethodId }
|
||||||
});
|
});
|
||||||
|
|
||||||
return Ok(configured.Select(ToModel).ToArray());
|
return Ok(configured.Select(ToModel).ToArray());
|
||||||
|
@ -80,20 +80,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
|
AutomatedPayoutConstants.ValidateInterval(ModelState, request.IntervalSeconds, nameof(request.IntervalSeconds));
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return this.CreateValidationError(ModelState);
|
return this.CreateValidationError(ModelState);
|
||||||
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
|
var paymentMethodId = PaymentMethodId.Parse(paymentMethod);
|
||||||
var activeProcessor =
|
var activeProcessor =
|
||||||
(await _payoutProcessorService.GetProcessors(
|
(await _payoutProcessorService.GetProcessors(
|
||||||
new PayoutProcessorService.PayoutProcessorQuery()
|
new PayoutProcessorService.PayoutProcessorQuery()
|
||||||
{
|
{
|
||||||
Stores = new[] { storeId },
|
Stores = new[] { storeId },
|
||||||
Processors = new[] { LightningAutomatedPayoutSenderFactory.ProcessorName },
|
Processors = new[] { LightningAutomatedPayoutSenderFactory.ProcessorName },
|
||||||
PaymentMethods = new[] { paymentMethod }
|
PaymentMethods = new[] { paymentMethodId }
|
||||||
}))
|
}))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
activeProcessor ??= new PayoutProcessorData();
|
activeProcessor ??= new PayoutProcessorData();
|
||||||
activeProcessor.HasTypedBlob<LightningAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
activeProcessor.HasTypedBlob<LightningAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
||||||
activeProcessor.StoreId = storeId;
|
activeProcessor.StoreId = storeId;
|
||||||
activeProcessor.PaymentMethod = paymentMethod;
|
activeProcessor.PaymentMethod = paymentMethodId.ToString();
|
||||||
activeProcessor.Processor = LightningAutomatedPayoutSenderFactory.ProcessorName;
|
activeProcessor.Processor = LightningAutomatedPayoutSenderFactory.ProcessorName;
|
||||||
var tcs = new TaskCompletionSource();
|
var tcs = new TaskCompletionSource();
|
||||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
||||||
|
|
|
@ -39,14 +39,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
public async Task<IActionResult> GetStoreOnChainAutomatedPayoutProcessors(
|
public async Task<IActionResult> GetStoreOnChainAutomatedPayoutProcessors(
|
||||||
string storeId, string? paymentMethod)
|
string storeId, string? paymentMethod)
|
||||||
{
|
{
|
||||||
paymentMethod = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod).ToString() : null;
|
var paymentMethodId = !string.IsNullOrEmpty(paymentMethod) ? PaymentMethodId.Parse(paymentMethod) : null;
|
||||||
var configured =
|
var configured =
|
||||||
await _payoutProcessorService.GetProcessors(
|
await _payoutProcessorService.GetProcessors(
|
||||||
new PayoutProcessorService.PayoutProcessorQuery()
|
new PayoutProcessorService.PayoutProcessorQuery()
|
||||||
{
|
{
|
||||||
Stores = new[] { storeId },
|
Stores = new[] { storeId },
|
||||||
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
||||||
PaymentMethods = paymentMethod is null ? null : new[] { paymentMethod }
|
PaymentMethods = paymentMethodId is null ? null : new[] { paymentMethodId }
|
||||||
});
|
});
|
||||||
|
|
||||||
return Ok(configured.Select(ToModel).ToArray());
|
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");
|
ModelState.AddModelError(nameof(request.FeeBlockTarget), "The feeBlockTarget should be between 1 and 1000");
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return this.CreateValidationError(ModelState);
|
return this.CreateValidationError(ModelState);
|
||||||
paymentMethod = PaymentMethodId.Parse(paymentMethod).ToString();
|
var paymentMethodId = PaymentMethodId.Parse(paymentMethod);
|
||||||
var activeProcessor =
|
var activeProcessor =
|
||||||
(await _payoutProcessorService.GetProcessors(
|
(await _payoutProcessorService.GetProcessors(
|
||||||
new PayoutProcessorService.PayoutProcessorQuery()
|
new PayoutProcessorService.PayoutProcessorQuery()
|
||||||
{
|
{
|
||||||
Stores = new[] { storeId },
|
Stores = new[] { storeId },
|
||||||
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
Processors = new[] { OnChainAutomatedPayoutSenderFactory.ProcessorName },
|
||||||
PaymentMethods = new[] { paymentMethod }
|
PaymentMethods = new[] { paymentMethodId }
|
||||||
}))
|
}))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
activeProcessor ??= new PayoutProcessorData();
|
activeProcessor ??= new PayoutProcessorData();
|
||||||
activeProcessor.HasTypedBlob<OnChainAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
activeProcessor.HasTypedBlob<OnChainAutomatedPayoutBlob>().SetBlob(FromModel(request));
|
||||||
activeProcessor.StoreId = storeId;
|
activeProcessor.StoreId = storeId;
|
||||||
activeProcessor.PaymentMethod = paymentMethod;
|
activeProcessor.PaymentMethod = paymentMethodId.ToString();
|
||||||
activeProcessor.Processor = OnChainAutomatedPayoutSenderFactory.ProcessorName;
|
activeProcessor.Processor = OnChainAutomatedPayoutSenderFactory.ProcessorName;
|
||||||
var tcs = new TaskCompletionSource();
|
var tcs = new TaskCompletionSource();
|
||||||
_eventAggregator.Publish(new PayoutProcessorUpdated()
|
_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.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
|
using BTCPayServer.ModelBinders;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.Greenfield
|
namespace BTCPayServer.Controllers.Greenfield
|
||||||
{
|
{
|
||||||
public partial class GreenfieldStoreOnChainPaymentMethodsController
|
public partial class GreenfieldStoreOnChainPaymentMethodsController
|
||||||
{
|
{
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[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)]
|
[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)
|
GenerateWalletRequest request)
|
||||||
{
|
{
|
||||||
|
|
||||||
AssertCryptoCodeWallet(cryptoCode, out var network, out _);
|
AssertCryptoCodeWallet(paymentMethodId, out var network, out _);
|
||||||
|
|
||||||
if (!_walletProvider.IsAvailable(network))
|
if (!_walletProvider.IsAvailable(network))
|
||||||
{
|
{
|
||||||
return this.CreateAPIError(503, "not-available",
|
return this.CreateAPIError(503, "not-available",
|
||||||
$"{cryptoCode} services are not currently available");
|
$"{paymentMethodId} services are not currently available");
|
||||||
}
|
}
|
||||||
|
|
||||||
var method = GetExistingBtcLikePaymentMethod(cryptoCode);
|
if (IsConfigured(paymentMethodId, out _))
|
||||||
if (method != null)
|
|
||||||
{
|
{
|
||||||
return this.CreateAPIError("already-configured",
|
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();
|
var canUseHotWallet = await CanUseHotWallet();
|
||||||
|
@ -64,13 +67,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
if (response == null)
|
if (response == null)
|
||||||
{
|
{
|
||||||
return this.CreateAPIError(503, "not-available",
|
return this.CreateAPIError(503, "not-available",
|
||||||
$"{cryptoCode} services are not currently available");
|
$"{paymentMethodId} services are not currently available");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
return this.CreateAPIError(503, "not-available",
|
return this.CreateAPIError(503, "not-available",
|
||||||
$"{cryptoCode} error: {e.Message}");
|
$"{paymentMethodId} error: {e.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var derivationSchemeSettings = new DerivationSchemeSettings(response.DerivationScheme, network);
|
var derivationSchemeSettings = new DerivationSchemeSettings(response.DerivationScheme, network);
|
||||||
|
@ -86,16 +89,22 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
|
||||||
var store = Store;
|
var store = Store;
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
store.SetSupportedPaymentMethod(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike),
|
var handler = _handlers[paymentMethodId];
|
||||||
|
store.SetPaymentMethodConfig(_handlers[paymentMethodId],
|
||||||
derivationSchemeSettings);
|
derivationSchemeSettings);
|
||||||
store.SetStoreBlob(storeBlob);
|
store.SetStoreBlob(storeBlob);
|
||||||
await _storeRepository.UpdateStore(store);
|
await _storeRepository.UpdateStore(store);
|
||||||
var rawResult = GetExistingBtcLikePaymentMethod(cryptoCode, store);
|
|
||||||
var result = new OnChainPaymentMethodDataWithSensitiveData(rawResult.CryptoCode, rawResult.DerivationScheme,
|
var result = new GenerateOnChainWalletResponse()
|
||||||
rawResult.Enabled, rawResult.Label, rawResult.AccountKeyPath, response.GetMnemonic(), derivationSchemeSettings.PaymentId.ToStringNormalized());
|
{
|
||||||
|
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()
|
_eventAggregator.Publish(new WalletChangedEvent()
|
||||||
{
|
{
|
||||||
WalletId = new WalletId(storeId, cryptoCode)
|
WalletId = new WalletId(storeId, network.CryptoCode)
|
||||||
});
|
});
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
@ -9,8 +10,11 @@ using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
|
using BTCPayServer.ModelBinders;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -19,6 +23,9 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
using NBXplorer.Models;
|
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;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.Greenfield
|
namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
@ -33,71 +40,28 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
public PoliciesSettings PoliciesSettings { get; }
|
public PoliciesSettings PoliciesSettings { get; }
|
||||||
|
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
||||||
private readonly BTCPayWalletProvider _walletProvider;
|
private readonly BTCPayWalletProvider _walletProvider;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
|
|
||||||
public GreenfieldStoreOnChainPaymentMethodsController(
|
public GreenfieldStoreOnChainPaymentMethodsController(
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
|
||||||
BTCPayWalletProvider walletProvider,
|
BTCPayWalletProvider walletProvider,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
ExplorerClientProvider explorerClientProvider,
|
ExplorerClientProvider explorerClientProvider,
|
||||||
PoliciesSettings policiesSettings,
|
PoliciesSettings policiesSettings,
|
||||||
|
PaymentMethodHandlerDictionary handlers,
|
||||||
EventAggregator eventAggregator)
|
EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
|
||||||
_walletProvider = walletProvider;
|
_walletProvider = walletProvider;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_explorerClientProvider = explorerClientProvider;
|
_explorerClientProvider = explorerClientProvider;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
PoliciesSettings = policiesSettings;
|
PoliciesSettings = policiesSettings;
|
||||||
}
|
_handlers = handlers;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JsonHttpException ErrorPaymentMethodNotConfigured()
|
protected JsonHttpException ErrorPaymentMethodNotConfigured()
|
||||||
|
@ -106,86 +70,64 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.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(
|
public IActionResult GetOnChainPaymentMethodPreview(
|
||||||
string storeId,
|
string storeId,
|
||||||
string cryptoCode,
|
[ModelBinder(typeof(PaymentMethodIdModelBinder))]
|
||||||
int offset = 0, int amount = 10)
|
PaymentMethodId paymentMethodId,
|
||||||
|
int offset = 0, int count = 10)
|
||||||
{
|
{
|
||||||
AssertCryptoCodeWallet(cryptoCode, out var network, out _);
|
AssertCryptoCodeWallet(paymentMethodId, out var network, out _);
|
||||||
|
if (!IsConfigured(paymentMethodId, out var settings))
|
||||||
var paymentMethod = GetExistingBtcLikePaymentMethod(cryptoCode);
|
|
||||||
if (string.IsNullOrEmpty(paymentMethod?.DerivationScheme))
|
|
||||||
{
|
{
|
||||||
throw ErrorPaymentMethodNotConfigured();
|
throw ErrorPaymentMethodNotConfigured();
|
||||||
}
|
}
|
||||||
|
return Ok(GetPreviewResultData(offset, count, network, settings.AccountDerivation));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[HttpPost("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview")]
|
[HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview")]
|
||||||
public IActionResult GetProposedOnChainPaymentMethodPreview(
|
public async Task<IActionResult> GetProposedOnChainPaymentMethodPreview(
|
||||||
string storeId,
|
string storeId,
|
||||||
string cryptoCode,
|
[ModelBinder(typeof(PaymentMethodIdModelBinder))]
|
||||||
[FromBody] UpdateOnChainPaymentMethodRequest paymentMethodData,
|
PaymentMethodId paymentMethodId,
|
||||||
int offset = 0, int amount = 10)
|
[FromBody] UpdatePaymentMethodRequest request = null,
|
||||||
|
int offset = 0, int count = 10)
|
||||||
{
|
{
|
||||||
AssertCryptoCodeWallet(cryptoCode, out var network, out _);
|
if (request is null)
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(paymentMethodData?.DerivationScheme))
|
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
ModelState.AddModelError(nameof(request), "Missing body");
|
||||||
"Missing derivationScheme");
|
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)
|
if (!ModelState.IsValid)
|
||||||
return this.CreateValidationError(ModelState);
|
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 deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||||
var line = strategy.GetLineFor(deposit);
|
var line = strategy.GetLineFor(deposit);
|
||||||
var result = new OnChainPaymentMethodPreviewResultData();
|
var result = new OnChainPaymentMethodPreviewResultData();
|
||||||
for (var i = offset; i < amount; i++)
|
for (var i = offset; i < count; i++)
|
||||||
{
|
{
|
||||||
var derivation = line.Derive((uint)i);
|
var derivation = line.Derive((uint)i);
|
||||||
result.Addresses.Add(
|
result.Addresses.Add(
|
||||||
|
@ -195,120 +137,32 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
{
|
{
|
||||||
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
|
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
|
||||||
Address =
|
Address =
|
||||||
network.NBXplorerNetwork.CreateAddress(strategy,deposit.GetKeyPath((uint)i), derivation.ScriptPubKey)
|
network.NBXplorerNetwork.CreateAddress(strategy, deposit.GetKeyPath((uint)i), derivation.ScriptPubKey)
|
||||||
.ToString()
|
.ToString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
return Ok(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
private void AssertCryptoCodeWallet(PaymentMethodId paymentMethodId, out BTCPayNetwork network, out BTCPayWallet wallet)
|
||||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}")]
|
|
||||||
public async Task<IActionResult> RemoveOnChainPaymentMethod(
|
|
||||||
string storeId,
|
|
||||||
string cryptoCode,
|
|
||||||
int offset = 0, int amount = 10)
|
|
||||||
{
|
{
|
||||||
AssertCryptoCodeWallet(cryptoCode, out _, out _);
|
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"));
|
||||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
network = handler.Network;
|
||||||
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"));
|
|
||||||
|
|
||||||
wallet = _walletProvider.GetWallet(network);
|
wallet = _walletProvider.GetWallet(network);
|
||||||
if (wallet is null)
|
if (wallet is null)
|
||||||
throw ErrorPaymentMethodNotConfigured();
|
throw ErrorPaymentMethodNotConfigured();
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnChainPaymentMethodData GetExistingBtcLikePaymentMethod(string cryptoCode, StoreData store = null)
|
bool IsConfigured(PaymentMethodId paymentMethodId, [MaybeNullWhen(false)] out DerivationSchemeSettings settings)
|
||||||
{
|
{
|
||||||
store ??= Store;
|
var store = Store;
|
||||||
var storeBlob = store.GetStoreBlob();
|
var conf = store.GetPaymentMethodConfig(paymentMethodId);
|
||||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
settings = null;
|
||||||
var paymentMethod = store
|
if (conf is (null or { Type: JTokenType.Null }))
|
||||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
return false;
|
||||||
.OfType<DerivationSchemeSettings>()
|
settings = ((BitcoinLikePaymentHandler)_handlers[paymentMethodId]).ParsePaymentMethodConfig(conf);
|
||||||
.FirstOrDefault(method => method.PaymentId == id);
|
return settings?.AccountDerivation is not null;
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,11 @@ using BTCPayServer.Data;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.Models.WalletViewModels;
|
using BTCPayServer.Models.WalletViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Payments.PayJoin;
|
using BTCPayServer.Payments.PayJoin;
|
||||||
using BTCPayServer.Payments.PayJoin.Sender;
|
using BTCPayServer.Payments.PayJoin.Sender;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Labels;
|
using BTCPayServer.Services.Labels;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -44,7 +46,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly WalletRepository _walletRepository;
|
private readonly WalletRepository _walletRepository;
|
||||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||||
private readonly NBXplorerDashboard _nbXplorerDashboard;
|
private readonly NBXplorerDashboard _nbXplorerDashboard;
|
||||||
|
@ -60,7 +62,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
public GreenfieldStoreOnChainWalletsController(
|
public GreenfieldStoreOnChainWalletsController(
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
BTCPayWalletProvider btcPayWalletProvider,
|
BTCPayWalletProvider btcPayWalletProvider,
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
PaymentMethodHandlerDictionary handlers,
|
||||||
WalletRepository walletRepository,
|
WalletRepository walletRepository,
|
||||||
ExplorerClientProvider explorerClientProvider,
|
ExplorerClientProvider explorerClientProvider,
|
||||||
NBXplorerDashboard nbXplorerDashboard,
|
NBXplorerDashboard nbXplorerDashboard,
|
||||||
|
@ -77,7 +79,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
{
|
{
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_btcPayWalletProvider = btcPayWalletProvider;
|
_btcPayWalletProvider = btcPayWalletProvider;
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
_handlers = handlers;
|
||||||
_walletRepository = walletRepository;
|
_walletRepository = walletRepository;
|
||||||
_explorerClientProvider = explorerClientProvider;
|
_explorerClientProvider = explorerClientProvider;
|
||||||
PoliciesSettings = policiesSettings;
|
PoliciesSettings = policiesSettings;
|
||||||
|
@ -316,7 +318,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation);
|
var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation);
|
||||||
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId,
|
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId,
|
||||||
utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
|
utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
|
||||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||||
return Ok(utxos.Select(coin =>
|
return Ok(utxos.Select(coin =>
|
||||||
{
|
{
|
||||||
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info1);
|
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
|
#pragma warning disable CS0612 // Type or member is obsolete
|
||||||
Labels = info?.LegacyLabels ?? new Dictionary<string, LabelData>(),
|
Labels = info?.LegacyLabels ?? new Dictionary<string, LabelData>(),
|
||||||
#pragma warning restore CS0612 // Type or member is obsolete
|
#pragma warning restore CS0612 // Type or member is obsolete
|
||||||
Link = _transactionLinkProviders.GetTransactionLink(pmi, coin.OutPoint.ToString()),
|
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, coin.OutPoint.ToString()),
|
||||||
Timestamp = coin.Timestamp,
|
Timestamp = coin.Timestamp,
|
||||||
KeyPath = coin.KeyPath,
|
KeyPath = coin.KeyPath,
|
||||||
Confirmations = coin.Confirmations,
|
Confirmations = coin.Confirmations,
|
||||||
|
@ -771,14 +773,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
[MaybeNullWhen(true)] out DerivationSchemeSettings derivationScheme,
|
[MaybeNullWhen(true)] out DerivationSchemeSettings derivationScheme,
|
||||||
[MaybeNullWhen(false)] out IActionResult actionResult)
|
[MaybeNullWhen(false)] out IActionResult actionResult)
|
||||||
{
|
{
|
||||||
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||||
derivationScheme = null;
|
derivationScheme = null;
|
||||||
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
if (!_handlers.TryGetValue(pmi, out var handler))
|
||||||
if (network is null)
|
|
||||||
{
|
{
|
||||||
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode",
|
throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode",
|
||||||
"This crypto code isn't set up in this BTCPay Server instance"));
|
"This crypto code isn't set up in this BTCPay Server instance"));
|
||||||
}
|
}
|
||||||
|
network = ((IHasNetwork)handler).Network;
|
||||||
|
|
||||||
if (!network.WalletSupported || !_btcPayWalletProvider.IsAvailable(network))
|
if (!network.WalletSupported || !_btcPayWalletProvider.IsAvailable(network))
|
||||||
{
|
{
|
||||||
|
@ -801,13 +803,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
|
||||||
private DerivationSchemeSettings? GetDerivationSchemeSettings(string cryptoCode)
|
private DerivationSchemeSettings? GetDerivationSchemeSettings(string cryptoCode)
|
||||||
{
|
{
|
||||||
var paymentMethod = Store
|
return Store.GetPaymentMethodConfig<DerivationSchemeSettings>(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), _handlers);
|
||||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
|
||||||
.OfType<DerivationSchemeSettings>()
|
|
||||||
.FirstOrDefault(p =>
|
|
||||||
p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike &&
|
|
||||||
p.PaymentId.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase));
|
|
||||||
return paymentMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnChainWalletTransactionData ToModel(WalletTransactionInfo? walletTransactionsInfoAsync,
|
private OnChainWalletTransactionData ToModel(WalletTransactionInfo? walletTransactionsInfoAsync,
|
||||||
|
|
|
@ -6,11 +6,18 @@ using BTCPayServer.Abstractions.Constants;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using StoreData = BTCPayServer.Data.StoreData;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
|
using BTCPayServer.ModelBinders;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Services.Stores;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.Greenfield
|
namespace BTCPayServer.Controllers.Greenfield
|
||||||
{
|
{
|
||||||
|
@ -20,37 +27,138 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
public class GreenfieldStorePaymentMethodsController : ControllerBase
|
public class GreenfieldStorePaymentMethodsController : ControllerBase
|
||||||
{
|
{
|
||||||
private StoreData Store => HttpContext.GetStoreData();
|
private StoreData Store => HttpContext.GetStoreData();
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
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;
|
_authorizationService = authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods")]
|
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}")]
|
||||||
public async Task<ActionResult<Dictionary<string, GenericPaymentMethodData>>> GetStorePaymentMethods(
|
public async Task<IActionResult> GetStorePaymentMethod(
|
||||||
string storeId,
|
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 storeBlob = Store.GetStoreBlob();
|
||||||
var excludedPaymentMethods = storeBlob.GetExcludedPaymentMethods();
|
var excludedPaymentMethods = storeBlob.GetExcludedPaymentMethods();
|
||||||
var canModifyStore = (await _authorizationService.AuthorizeAsync(User, null,
|
|
||||||
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;
|
if (includeConfig is true)
|
||||||
;
|
{
|
||||||
return Ok(Store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
var canModifyStore = (await _authorizationService.AuthorizeAsync(User, null,
|
||||||
.Where(method =>
|
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;
|
||||||
enabled is null || (enabled is false && excludedPaymentMethods.Match(method.PaymentId)))
|
if (!canModifyStore)
|
||||||
.ToDictionary(
|
return this.CreateAPIPermissionError(Policies.CanModifyStoreSettings);
|
||||||
method => method.PaymentId.ToStringNormalized(),
|
}
|
||||||
|
|
||||||
|
return Ok(Store.GetPaymentMethodConfigs(_handlers, onlyEnabled is true)
|
||||||
|
.Select(
|
||||||
method => new GenericPaymentMethodData()
|
method => new GenericPaymentMethodData()
|
||||||
{
|
{
|
||||||
CryptoCode = method.PaymentId.CryptoCode,
|
PaymentMethodId = method.Key.ToString(),
|
||||||
Enabled = enabled.GetValueOrDefault(!excludedPaymentMethods.Match(method.PaymentId)),
|
Enabled = onlyEnabled.GetValueOrDefault(!excludedPaymentMethods.Match(method.Key)),
|
||||||
Data = method.PaymentId.PaymentType.GetGreenfieldData(method, canModifyStore)
|
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.Abstractions.Constants;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.PayoutProcessors;
|
using BTCPayServer.PayoutProcessors;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
@ -53,7 +54,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
{
|
{
|
||||||
Stores = new[] { storeId },
|
Stores = new[] { storeId },
|
||||||
Processors = new[] { processor },
|
Processors = new[] { processor },
|
||||||
PaymentMethods = new[] { paymentMethod }
|
PaymentMethods = new[] { PaymentMethodId.Parse(paymentMethod) }
|
||||||
})).FirstOrDefault();
|
})).FirstOrDefault();
|
||||||
if (matched is null)
|
if (matched is null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -124,7 +124,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
Archived = data.Archived,
|
Archived = data.Archived,
|
||||||
SupportUrl = storeBlob.StoreSupportUrl,
|
SupportUrl = storeBlob.StoreSupportUrl,
|
||||||
SpeedPolicy = data.SpeedPolicy,
|
SpeedPolicy = data.SpeedPolicy,
|
||||||
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToStringNormalized(),
|
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToString(),
|
||||||
//blob
|
//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 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
|
//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,
|
Above = criteria.Above,
|
||||||
Amount = criteria.Value.Value,
|
Amount = criteria.Value.Value,
|
||||||
CurrencyCode = criteria.Value.Currency,
|
CurrencyCode = criteria.Value.Currency,
|
||||||
PaymentMethod = criteria.PaymentMethod.ToStringNormalized()
|
PaymentMethod = criteria.PaymentMethod.ToString()
|
||||||
}).ToList() ?? new List<PaymentMethodCriteriaData>()
|
}).ToList() ?? new List<PaymentMethodCriteriaData>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using NBXplorer.DerivationStrategy;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||||
|
@ -559,54 +560,21 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
return result.Value ?? GetFromActionResult<T>(result.Result);
|
return result.Value ?? GetFromActionResult<T>(result.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
|
public override async Task<OnChainPaymentMethodPreviewResultData> PreviewProposedStoreOnChainPaymentMethodAddresses(
|
||||||
bool? enabled, CancellationToken token = default)
|
string storeId, string paymentMethodId,
|
||||||
{
|
string derivationScheme, int offset = 0, int count = 10,
|
||||||
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,
|
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
HandleActionResult(await GetController<GreenfieldStoreOnChainPaymentMethodsController>().RemoveOnChainPaymentMethod(storeId, cryptoCode));
|
return GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||||
}
|
await GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetProposedOnChainPaymentMethodPreview(storeId, Payments.PaymentMethodId.Parse(paymentMethodId),
|
||||||
|
new UpdatePaymentMethodRequest() { Config = JValue.CreateString(derivationScheme) }, offset, count));
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<OnChainPaymentMethodPreviewResultData> PreviewStoreOnChainPaymentMethodAddresses(
|
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>(
|
return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
|
||||||
GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetOnChainPaymentMethodPreview(storeId, cryptoCode, offset,
|
GetController<GreenfieldStoreOnChainPaymentMethodsController>().GetOnChainPaymentMethodPreview(storeId, Payments.PaymentMethodId.Parse(paymentMethodId), offset,
|
||||||
amount)));
|
amount)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -814,71 +782,6 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
return GetFromActionResult<StoreData>(await GetController<GreenfieldStoresController>().UpdateStore(storeId, request));
|
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,
|
public override async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null,
|
||||||
InvoiceStatus[] status = null,
|
InvoiceStatus[] status = null,
|
||||||
DateTimeOffset? startDate = null,
|
DateTimeOffset? startDate = null,
|
||||||
|
@ -982,18 +885,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
return Task.FromResult(GetFromActionResult<PermissionMetadata[]>(GetController<UIHomeController>().Permissions()));
|
return Task.FromResult(GetFromActionResult<PermissionMetadata[]>(GetController<UIHomeController>().Permissions()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId,
|
public override async Task<GenericPaymentMethodData[]> GetStorePaymentMethods(string storeId,
|
||||||
bool? enabled = null, CancellationToken token = default)
|
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,
|
public override async Task<GenerateOnChainWalletResponse> GenerateOnChainWallet(string storeId,
|
||||||
string cryptoCode, GenerateOnChainWalletRequest request,
|
string paymentMethodId, GenerateOnChainWalletRequest request,
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return GetFromActionResult<OnChainPaymentMethodDataWithSensitiveData>(
|
return GetFromActionResult<GenerateOnChainWalletResponse>(
|
||||||
await GetController<GreenfieldStoreOnChainPaymentMethodsController>().GenerateOnChainWallet(storeId, cryptoCode,
|
await GetController<GreenfieldStoreOnChainPaymentMethodsController>().GenerateOnChainWallet(storeId, Payments.PaymentMethodId.Parse(paymentMethodId),
|
||||||
new GenerateWalletRequest()
|
new GenerateWalletRequest()
|
||||||
{
|
{
|
||||||
Passphrase = request.Passphrase,
|
Passphrase = request.Passphrase,
|
||||||
|
|
|
@ -6,6 +6,8 @@ using BTCPayServer.Data;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
@ -41,60 +43,61 @@ namespace BTCPayServer.Controllers
|
||||||
var btcpayNetwork = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
var btcpayNetwork = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||||
var network = btcpayNetwork.NBitcoinNetwork;
|
var network = btcpayNetwork.NBitcoinNetwork;
|
||||||
var paymentMethodId = new[] { store.GetDefaultPaymentId() }
|
var paymentMethodId = new[] { store.GetDefaultPaymentId() }
|
||||||
.Concat(store.GetEnabledPaymentIds(_NetworkProvider))
|
.Concat(store.GetEnabledPaymentIds())
|
||||||
.FirstOrDefault(p => p?.ToString() == request.PaymentMethodId);
|
.FirstOrDefault(p => p?.ToString() == request.PaymentMethodId);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
var paymentMethod = invoice.GetPaymentPrompt(paymentMethodId);
|
||||||
var destination = paymentMethod?.GetPaymentMethodDetails().GetPaymentDestination();
|
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 address = BitcoinAddress.Create(destination, network);
|
var txid = (await cheater.GetCashCow(cryptoCode).SendToAddressAsync(address, amount)).ToString();
|
||||||
var txid = (await cheater.CashCow.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
|
return Ok(new
|
||||||
{
|
{
|
||||||
Txid = txid,
|
Txid = paymentHash,
|
||||||
AmountRemaining = paymentMethod.Calculate().Due - amount.ToDecimal(MoneyUnit.BTC),
|
AmountRemaining = paymentMethod.Calculate().TotalDue - paid,
|
||||||
SuccessMessage = $"Created transaction {txid}"
|
SuccessMessage = $"Sent payment {paymentHash}"
|
||||||
});
|
|
||||||
|
|
||||||
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"
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return UnprocessableEntity(new
|
||||||
|
{
|
||||||
|
ErrorMessage = response.ErrorDetail
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UnprocessableEntity(new
|
||||||
|
{
|
||||||
|
ErrorMessage = $"Payment method {paymentMethodId} is not supported"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -109,12 +112,12 @@ namespace BTCPayServer.Controllers
|
||||||
[CheatModeRoute]
|
[CheatModeRoute]
|
||||||
public IActionResult MineBlock(string invoiceId, MineBlocksRequest request, [FromServices] Cheater cheater)
|
public IActionResult MineBlock(string invoiceId, MineBlocksRequest request, [FromServices] Cheater cheater)
|
||||||
{
|
{
|
||||||
var blockRewardBitcoinAddress = cheater.CashCow.GetNewAddress();
|
var blockRewardBitcoinAddress = cheater.GetCashCow(request.CryptoCode).GetNewAddress();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (request.BlockCount > 0)
|
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 Ok(new { SuccessMessage = $"Mined {request.BlockCount} block{(request.BlockCount == 1 ? "" : "s")} " });
|
||||||
}
|
}
|
||||||
return BadRequest(new { ErrorMessage = "Number of blocks should be at least 1" });
|
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.InvoicingModels;
|
||||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
|
@ -118,7 +120,7 @@ namespace BTCPayServer.Controllers
|
||||||
var additionalData = metaData
|
var additionalData = metaData
|
||||||
.Where(dict => !InvoiceAdditionalDataExclude.Contains(dict.Key))
|
.Where(dict => !InvoiceAdditionalDataExclude.Contains(dict.Key))
|
||||||
.ToDictionary(dict => dict.Key, dict => dict.Value);
|
.ToDictionary(dict => dict.Key, dict => dict.Value);
|
||||||
|
|
||||||
var model = new InvoiceDetailsModel
|
var model = new InvoiceDetailsModel
|
||||||
{
|
{
|
||||||
StoreId = store.Id,
|
StoreId = store.Id,
|
||||||
|
@ -131,7 +133,6 @@ namespace BTCPayServer.Controllers
|
||||||
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
|
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
|
||||||
invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" :
|
invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" :
|
||||||
"low",
|
"low",
|
||||||
RefundEmail = invoice.RefundMail,
|
|
||||||
CreatedDate = invoice.InvoiceTime,
|
CreatedDate = invoice.InvoiceTime,
|
||||||
ExpirationDate = invoice.ExpirationTime,
|
ExpirationDate = invoice.ExpirationTime,
|
||||||
MonitoringDate = invoice.MonitoringExpiration,
|
MonitoringDate = invoice.MonitoringExpiration,
|
||||||
|
@ -162,13 +163,13 @@ namespace BTCPayServer.Controllers
|
||||||
model.Overpaid = details.Overpaid;
|
model.Overpaid = details.Overpaid;
|
||||||
model.StillDue = details.StillDue;
|
model.StillDue = details.StillDue;
|
||||||
model.HasRates = details.HasRates;
|
model.HasRates = details.HasRates;
|
||||||
|
|
||||||
if (additionalData.ContainsKey("receiptData"))
|
if (additionalData.ContainsKey("receiptData"))
|
||||||
{
|
{
|
||||||
model.ReceiptData = (Dictionary<string, object>)additionalData["receiptData"];
|
model.ReceiptData = (Dictionary<string, object>)additionalData["receiptData"];
|
||||||
additionalData.Remove("receiptData");
|
additionalData.Remove("receiptData");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (additionalData.ContainsKey("posData") && additionalData["posData"] is string posData)
|
if (additionalData.ContainsKey("posData") && additionalData["posData"] is string posData)
|
||||||
{
|
{
|
||||||
// overwrite with parsed JSON if possible
|
// overwrite with parsed JSON if possible
|
||||||
|
@ -181,7 +182,7 @@ namespace BTCPayServer.Controllers
|
||||||
additionalData["posData"] = posData;
|
additionalData["posData"] = posData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model.AdditionalData = additionalData;
|
model.AdditionalData = additionalData;
|
||||||
|
|
||||||
return View(model);
|
return View(model);
|
||||||
|
@ -204,7 +205,7 @@ namespace BTCPayServer.Controllers
|
||||||
if (i.RedirectURL is not null)
|
if (i.RedirectURL is not null)
|
||||||
{
|
{
|
||||||
return Redirect(i.RedirectURL.ToString());
|
return Redirect(i.RedirectURL.ToString());
|
||||||
}
|
}
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -230,7 +231,7 @@ namespace BTCPayServer.Controllers
|
||||||
JToken? receiptData = null;
|
JToken? receiptData = null;
|
||||||
i.Metadata?.AdditionalData?.TryGetValue("receiptData", out receiptData);
|
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.Amount = i.PaidAmount.Net;
|
||||||
vm.Payments = receipt.ShowPayments is false ? null : payments;
|
vm.Payments = receipt.ShowPayments is false ? null : payments;
|
||||||
|
@ -266,12 +267,23 @@ namespace BTCPayServer.Controllers
|
||||||
new { pullPaymentId = ppId });
|
new { pullPaymentId = ppId });
|
||||||
}
|
}
|
||||||
|
|
||||||
var paymentMethods = invoice.GetBlob(_NetworkProvider).GetPaymentMethods();
|
var paymentMethods = invoice.GetBlob().GetPaymentPrompts();
|
||||||
var pmis = paymentMethods.Select(method => method.GetId()).ToList();
|
var pmis = paymentMethods.Select(method => method.PaymentMethodId).ToHashSet();
|
||||||
pmis = pmis.Concat(pmis.Where(id => id.PaymentType == LNURLPayPaymentType.Instance)
|
// If LNURL is contained, add the LN too as a possible option
|
||||||
.Select(id => new PaymentMethodId(id.CryptoCode, LightningPaymentType.Instance))).ToList();
|
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 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())
|
if (!options.Any())
|
||||||
{
|
{
|
||||||
var vm = new RefundModel { Title = "No matching payment method" };
|
var vm = new RefundModel { Title = "No matching payment method" };
|
||||||
|
@ -281,15 +293,15 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultRefund = invoice.Payments
|
var defaultRefund = invoice.Payments
|
||||||
.Select(p => p.GetBlob(_NetworkProvider))
|
.Select(p => p.GetBlob())
|
||||||
.Select(p => p?.GetPaymentMethodId())
|
.Select(p => p.PaymentMethodId)
|
||||||
.FirstOrDefault(p => p != null && options.Contains(p));
|
.FirstOrDefault(p => p != null && options.Contains(p));
|
||||||
|
|
||||||
var refund = new RefundModel
|
var refund = new RefundModel
|
||||||
{
|
{
|
||||||
Title = "Payment method",
|
Title = "Payment method",
|
||||||
AvailablePaymentMethods =
|
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"),
|
"Value", "Text"),
|
||||||
SelectedPaymentMethod = defaultRefund?.ToString() ?? options.First().ToString()
|
SelectedPaymentMethod = defaultRefund?.ToString() ?? options.First().ToString()
|
||||||
};
|
};
|
||||||
|
@ -318,71 +330,67 @@ namespace BTCPayServer.Controllers
|
||||||
var store = GetCurrentStore();
|
var store = GetCurrentStore();
|
||||||
var paymentMethodId = PaymentMethodId.Parse(model.SelectedPaymentMethod);
|
var paymentMethodId = PaymentMethodId.Parse(model.SelectedPaymentMethod);
|
||||||
var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.Currency, true);
|
var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.Currency, true);
|
||||||
var paymentMethodDivisibility = _CurrencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
|
|
||||||
RateRules rules;
|
RateRules rules;
|
||||||
RateResult rateResult;
|
RateResult rateResult;
|
||||||
CreatePullPayment createPullPayment;
|
CreatePullPayment createPullPayment;
|
||||||
PaymentMethodAccounting accounting;
|
var pms = invoice.GetPaymentPrompts();
|
||||||
var pms = invoice.GetPaymentMethods();
|
if (!pms.TryGetValue(paymentMethodId, out var paymentMethod))
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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)
|
var accounting = paymentMethod.Calculate();
|
||||||
{
|
decimal cryptoPaid = accounting.Paid;
|
||||||
accounting = paymentMethod.Calculate();
|
decimal dueAmount = accounting.TotalDue;
|
||||||
cryptoPaid = accounting.Paid;
|
var paymentMethodCurrency = paymentMethodId.CryptoCode;
|
||||||
dueAmount = accounting.TotalDue;
|
|
||||||
paidAmount = cryptoPaid.RoundToSignificant(appliedDivisibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
var isPaidOver = invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver;
|
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)
|
switch (model.RefundStep)
|
||||||
{
|
{
|
||||||
case RefundSteps.SelectPaymentMethod:
|
case RefundSteps.SelectPaymentMethod:
|
||||||
model.RefundStep = RefundSteps.SelectRate;
|
model.RefundStep = RefundSteps.SelectRate;
|
||||||
model.Title = "How much to refund?";
|
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);
|
ModelState.AddModelError(nameof(model.SelectedRefundOption),
|
||||||
model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethodDivisibility);
|
$"Impossible to fetch rate: {rateResult.EvaluatedRule}");
|
||||||
model.RateThenText = _displayFormatter.Currency(model.CryptoAmountThen, paymentMethodId.CryptoCode);
|
return View("_RefundModal", model);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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.InvoiceDivisibility = cdCurrency.Divisibility;
|
||||||
model.InvoiceCurrency = invoice.Currency;
|
model.InvoiceCurrency = invoice.Currency;
|
||||||
model.CustomAmount = model.FiatAmount;
|
model.CustomAmount = model.FiatAmount;
|
||||||
model.CustomCurrency = invoice.Currency;
|
model.CustomCurrency = invoice.Currency;
|
||||||
model.SubtractPercentage = 0;
|
model.SubtractPercentage = 0;
|
||||||
model.OverpaidAmount = overpaidAmount;
|
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);
|
model.FiatText = _displayFormatter.Currency(model.FiatAmount, invoice.Currency);
|
||||||
return View("_RefundModal", model);
|
return View("_RefundModal", model);
|
||||||
|
|
||||||
|
@ -405,32 +413,32 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
return View("_RefundModal", model);
|
return View("_RefundModal", model);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (model.SelectedRefundOption)
|
switch (model.SelectedRefundOption)
|
||||||
{
|
{
|
||||||
case "RateThen":
|
case "RateThen":
|
||||||
createPullPayment.Currency = paymentMethodId.CryptoCode;
|
createPullPayment.Currency = paymentMethodCurrency;
|
||||||
createPullPayment.Amount = model.CryptoAmountThen;
|
createPullPayment.Amount = model.CryptoAmountThen;
|
||||||
createPullPayment.AutoApproveClaims = authorizedForAutoApprove;
|
createPullPayment.AutoApproveClaims = authorizedForAutoApprove;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "CurrentRate":
|
case "CurrentRate":
|
||||||
createPullPayment.Currency = paymentMethodId.CryptoCode;
|
createPullPayment.Currency = paymentMethodCurrency;
|
||||||
createPullPayment.Amount = model.CryptoAmountNow;
|
createPullPayment.Amount = model.CryptoAmountNow;
|
||||||
createPullPayment.AutoApproveClaims = authorizedForAutoApprove;
|
createPullPayment.AutoApproveClaims = authorizedForAutoApprove;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Fiat":
|
case "Fiat":
|
||||||
appliedDivisibility = cdCurrency.Divisibility;
|
ppDivisibility = cdCurrency.Divisibility;
|
||||||
createPullPayment.Currency = invoice.Currency;
|
createPullPayment.Currency = invoice.Currency;
|
||||||
createPullPayment.Amount = model.FiatAmount;
|
createPullPayment.Amount = model.FiatAmount;
|
||||||
createPullPayment.AutoApproveClaims = false;
|
createPullPayment.AutoApproveClaims = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "OverpaidAmount":
|
case "OverpaidAmount":
|
||||||
model.Title = "How much to refund?";
|
model.Title = "How much to refund?";
|
||||||
model.RefundStep = RefundSteps.SelectRate;
|
model.RefundStep = RefundSteps.SelectRate;
|
||||||
|
|
||||||
if (!isPaidOver)
|
if (!isPaidOver)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(model.SelectedRefundOption), "Invoice is not overpaid");
|
ModelState.AddModelError(nameof(model.SelectedRefundOption), "Invoice is not overpaid");
|
||||||
|
@ -443,8 +451,8 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
return View("_RefundModal", model);
|
return View("_RefundModal", model);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPullPayment.Currency = paymentMethodId.CryptoCode;
|
createPullPayment.Currency = paymentMethodCurrency;
|
||||||
createPullPayment.Amount = overpaidAmount!.Value;
|
createPullPayment.Amount = overpaidAmount!.Value;
|
||||||
createPullPayment.AutoApproveClaims = true;
|
createPullPayment.AutoApproveClaims = true;
|
||||||
break;
|
break;
|
||||||
|
@ -469,7 +477,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
rules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
rules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
||||||
rateResult = await _RateProvider.FetchRate(
|
rateResult = await _RateProvider.FetchRate(
|
||||||
new CurrencyPair(paymentMethodId.CryptoCode, model.CustomCurrency), rules,
|
new CurrencyPair(paymentMethodCurrency, model.CustomCurrency), rules,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
//TODO: What if fetching rate failed?
|
//TODO: What if fetching rate failed?
|
||||||
|
@ -482,7 +490,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
createPullPayment.Currency = model.CustomCurrency;
|
createPullPayment.Currency = model.CustomCurrency;
|
||||||
createPullPayment.Amount = model.CustomAmount;
|
createPullPayment.Amount = model.CustomAmount;
|
||||||
createPullPayment.AutoApproveClaims = authorizedForAutoApprove && paymentMethodId.CryptoCode == model.CustomCurrency;
|
createPullPayment.AutoApproveClaims = authorizedForAutoApprove && paymentMethodCurrency == model.CustomCurrency;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -499,7 +507,7 @@ namespace BTCPayServer.Controllers
|
||||||
if (model.SubtractPercentage is > 0 and <= 100)
|
if (model.SubtractPercentage is > 0 and <= 100)
|
||||||
{
|
{
|
||||||
var reduceByAmount = createPullPayment.Amount * (model.SubtractPercentage / 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);
|
var ppId = await _paymentHostedService.CreatePullPayment(createPullPayment);
|
||||||
|
@ -531,30 +539,33 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
Archived = invoice.Archived,
|
Archived = invoice.Archived,
|
||||||
Payments = invoice.GetPayments(false),
|
Payments = invoice.GetPayments(false),
|
||||||
CryptoPayments = invoice.GetPaymentMethods().Select(
|
CryptoPayments = invoice.GetPaymentPrompts().Select(
|
||||||
data =>
|
data =>
|
||||||
{
|
{
|
||||||
var accounting = data.Calculate();
|
var accounting = data.Calculate();
|
||||||
var paymentMethodId = data.GetId();
|
var paymentMethodId = data.PaymentMethodId;
|
||||||
var hasPayment = accounting.CryptoPaid > 0;
|
var hasPayment = accounting.PaymentMethodPaid > 0;
|
||||||
var overpaidAmount = accounting.OverpaidHelper;
|
var overpaidAmount = accounting.OverpaidHelper;
|
||||||
var rate = ExchangeRate(data.GetId().CryptoCode, data);
|
var rate = ExchangeRate(data.Currency, data);
|
||||||
|
|
||||||
if (rate is not null) hasRates = true;
|
if (rate is not null)
|
||||||
if (hasPayment && overpaidAmount > 0) overpaid = true;
|
hasRates = true;
|
||||||
if (hasPayment && accounting.Due > 0) stillDue = true;
|
if (hasPayment && overpaidAmount > 0)
|
||||||
|
overpaid = true;
|
||||||
|
if (hasPayment && accounting.Due > 0)
|
||||||
|
stillDue = true;
|
||||||
|
|
||||||
return new InvoiceDetailsModel.CryptoPayment
|
return new InvoiceDetailsModel.CryptoPayment
|
||||||
{
|
{
|
||||||
Rate = rate,
|
Rate = rate,
|
||||||
PaymentMethodRaw = data,
|
PaymentMethodRaw = data,
|
||||||
PaymentMethodId = paymentMethodId,
|
PaymentMethodId = paymentMethodId,
|
||||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
PaymentMethod = paymentMethodId.ToString(),
|
||||||
TotalDue = _displayFormatter.Currency(accounting.TotalDue, paymentMethodId.CryptoCode),
|
TotalDue = _displayFormatter.Currency(accounting.TotalDue, data.Currency),
|
||||||
Due = hasPayment ? _displayFormatter.Currency(accounting.Due, paymentMethodId.CryptoCode) : null,
|
Due = hasPayment ? _displayFormatter.Currency(accounting.Due, data.Currency) : null,
|
||||||
Paid = hasPayment ? _displayFormatter.Currency(accounting.CryptoPaid, paymentMethodId.CryptoCode) : null,
|
Paid = hasPayment ? _displayFormatter.Currency(accounting.PaymentMethodPaid, data.Currency) : null,
|
||||||
Overpaid = hasPayment ? _displayFormatter.Currency(overpaidAmount, paymentMethodId.CryptoCode) : null,
|
Overpaid = hasPayment ? _displayFormatter.Currency(overpaidAmount, data.Currency) : null,
|
||||||
Address = data.GetPaymentMethodDetails().GetPaymentDestination()
|
Address = data.Destination
|
||||||
};
|
};
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
Overpaid = overpaid,
|
Overpaid = overpaid,
|
||||||
|
@ -620,11 +631,12 @@ namespace BTCPayServer.Controllers
|
||||||
if (!GetCurrentStore().HasPermission(GetUserId(), Policies.CanModifyStoreSettings))
|
if (!GetCurrentStore().HasPermission(GetUserId(), Policies.CanModifyStoreSettings))
|
||||||
return Forbid();
|
return Forbid();
|
||||||
|
|
||||||
var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_NetworkProvider, network.CryptoCode))?.AccountDerivation;
|
var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_handlers, network.CryptoCode))?.AccountDerivation;
|
||||||
if (derivationScheme is null)
|
if (derivationScheme is null)
|
||||||
return NotSupported("This feature is only available to BTC wallets");
|
return NotSupported("This feature is only available to BTC wallets");
|
||||||
|
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
|
||||||
var bumpableAddresses = (await GetAddresses(selectedItems))
|
var bumpableAddresses = (await GetAddresses(selectedItems))
|
||||||
.Where(p => p.GetPaymentMethodId().IsBTCOnChain)
|
.Where(p => p.GetPaymentMethodId() == btc)
|
||||||
.Select(p => p.GetAddress()).ToHashSet();
|
.Select(p => p.GetAddress()).ToHashSet();
|
||||||
var utxos = await explorer.GetUTXOsAsync(derivationScheme);
|
var utxos = await explorer.GetUTXOsAsync(derivationScheme);
|
||||||
var bumpableUTXOs = utxos.GetUnspentUTXOs().Where(u => u.Confirmations == 0 && bumpableAddresses.Contains(u.ScriptPubKey.Hash.ToString())).ToArray();
|
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;
|
bool isDefaultPaymentId = false;
|
||||||
var storeBlob = store.GetStoreBlob();
|
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
|
// Exclude Lightning if OnChainWithLnInvoiceFallback is active and we have both payment methods
|
||||||
if (storeBlob is { OnChainWithLnInvoiceFallback: true } &&
|
if (storeBlob is { OnChainWithLnInvoiceFallback: true } &&
|
||||||
|
@ -729,11 +742,12 @@ namespace BTCPayServer.Controllers
|
||||||
if (displayedPaymentMethods.Contains(lnId) && displayedPaymentMethods.Contains(lnurlId))
|
if (displayedPaymentMethods.Contains(lnId) && displayedPaymentMethods.Contains(lnurlId))
|
||||||
displayedPaymentMethods.Remove(lnurlId);
|
displayedPaymentMethods.Remove(lnurlId);
|
||||||
|
|
||||||
|
|
||||||
if (paymentMethodId is not null && !displayedPaymentMethods.Contains(paymentMethodId))
|
if (paymentMethodId is not null && !displayedPaymentMethods.Contains(paymentMethodId))
|
||||||
paymentMethodId = null;
|
paymentMethodId = null;
|
||||||
if (paymentMethodId is null)
|
if (paymentMethodId is null)
|
||||||
{
|
{
|
||||||
PaymentMethodId? invoicePaymentId = invoice.GetDefaultPaymentMethod();
|
PaymentMethodId? invoicePaymentId = invoice.DefaultPaymentMethod;
|
||||||
PaymentMethodId? storePaymentId = store.GetDefaultPaymentId();
|
PaymentMethodId? storePaymentId = store.GetDefaultPaymentId();
|
||||||
if (invoicePaymentId is not null)
|
if (invoicePaymentId is not null)
|
||||||
{
|
{
|
||||||
|
@ -755,56 +769,55 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
if (paymentMethodId is null)
|
if (paymentMethodId is null)
|
||||||
{
|
{
|
||||||
paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
|
var defaultBTC = PaymentTypes.CHAIN.GetPaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode);
|
||||||
displayedPaymentMethods.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType != PaymentTypes.LNURLPay) ??
|
var defaultLNURLPay = PaymentTypes.LNURL.GetPaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode);
|
||||||
|
paymentMethodId = displayedPaymentMethods.FirstOrDefault(e => e == defaultBTC) ??
|
||||||
|
displayedPaymentMethods.FirstOrDefault(e => e == defaultLNURLPay) ??
|
||||||
displayedPaymentMethods.FirstOrDefault();
|
displayedPaymentMethods.FirstOrDefault();
|
||||||
}
|
}
|
||||||
isDefaultPaymentId = true;
|
isDefaultPaymentId = true;
|
||||||
}
|
}
|
||||||
if (paymentMethodId is null)
|
if (paymentMethodId is null)
|
||||||
return null;
|
return null;
|
||||||
|
if (!invoice.Support(paymentMethodId))
|
||||||
BTCPayNetworkBase network = _NetworkProvider.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
|
|
||||||
if (network is null || !invoice.Support(paymentMethodId))
|
|
||||||
{
|
{
|
||||||
if (!isDefaultPaymentId)
|
if (!isDefaultPaymentId)
|
||||||
return null;
|
return null;
|
||||||
var paymentMethodTemp = invoice
|
var paymentMethodTemp = invoice
|
||||||
.GetPaymentMethods()
|
.GetPaymentPrompts()
|
||||||
.Where(p => displayedPaymentMethods.Contains(p.GetId()))
|
.Where(p => displayedPaymentMethods.Contains(p.PaymentMethodId))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
if (paymentMethodTemp is null)
|
if (paymentMethodTemp is null)
|
||||||
return null;
|
return null;
|
||||||
network = paymentMethodTemp.Network;
|
paymentMethodId = paymentMethodTemp.PaymentMethodId;
|
||||||
paymentMethodId = paymentMethodTemp.GetId();
|
|
||||||
}
|
}
|
||||||
|
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)
|
// We activate the default payment method, and also those which aren't displayed (as they can't be set as default)
|
||||||
bool activated = false;
|
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))
|
if (pmi != paymentMethodId || !displayedPaymentMethods.Contains(pmi))
|
||||||
continue;
|
continue;
|
||||||
var pmd = pm.GetPaymentMethodDetails();
|
if (!pm.Activated)
|
||||||
if (!pmd.Activated)
|
|
||||||
{
|
{
|
||||||
if (await _invoiceActivator.ActivateInvoicePaymentMethod(pmi, invoice, store))
|
if (await _invoiceActivator.ActivateInvoicePaymentMethod(invoice.Id, pmi))
|
||||||
{
|
{
|
||||||
activated = true;
|
activated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (prompt is null)
|
||||||
|
return null;
|
||||||
if (activated)
|
if (activated)
|
||||||
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
||||||
|
|
||||||
|
var accounting = prompt.Calculate();
|
||||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
|
||||||
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
|
||||||
var dto = invoice.EntityToDTO();
|
|
||||||
var accounting = paymentMethod.Calculate();
|
|
||||||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
|
||||||
|
|
||||||
switch (lang?.ToLowerInvariant())
|
switch (lang?.ToLowerInvariant())
|
||||||
{
|
{
|
||||||
|
@ -841,10 +854,21 @@ namespace BTCPayServer.Controllers
|
||||||
.Replace("{InvoiceId}", Uri.EscapeDataString(invoice.Id))
|
.Replace("{InvoiceId}", Uri.EscapeDataString(invoice.Id))
|
||||||
: null;
|
: 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
|
var model = new PaymentModel
|
||||||
{
|
{
|
||||||
Activated = paymentMethodDetails.Activated,
|
Activated = prompt.Activated,
|
||||||
CryptoCode = network.CryptoCode,
|
PaymentMethodName = GetPaymentMethodName(paymentMethodId),
|
||||||
|
CryptoCode = prompt.Currency,
|
||||||
RootPath = Request.PathBase.Value.WithTrailingSlash(),
|
RootPath = Request.PathBase.Value.WithTrailingSlash(),
|
||||||
OrderId = orderId,
|
OrderId = orderId,
|
||||||
InvoiceId = invoiceId,
|
InvoiceId = invoiceId,
|
||||||
|
@ -858,21 +882,21 @@ namespace BTCPayServer.Controllers
|
||||||
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
|
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
|
||||||
CelebratePayment = storeBlob.CelebratePayment,
|
CelebratePayment = storeBlob.CelebratePayment,
|
||||||
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback,
|
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback,
|
||||||
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
|
CryptoImage = Request.GetRelativePathOrAbsolute(GetPaymentMethodImage(paymentMethodId)),
|
||||||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
BtcAddress = prompt.Destination,
|
||||||
BtcDue = accounting.ShowMoney(accounting.Due),
|
BtcDue = accounting.ShowMoney(accounting.Due),
|
||||||
BtcPaid = accounting.ShowMoney(accounting.Paid),
|
BtcPaid = accounting.ShowMoney(accounting.Paid),
|
||||||
InvoiceCurrency = invoice.Currency,
|
InvoiceCurrency = invoice.Currency,
|
||||||
OrderAmount = accounting.ShowMoney(accounting.TotalDue - accounting.NetworkFee),
|
OrderAmount = accounting.ShowMoney(accounting.TotalDue - accounting.PaymentMethodFee),
|
||||||
IsUnsetTopUp = invoice.IsUnsetTopUp(),
|
IsUnsetTopUp = invoice.IsUnsetTopUp(),
|
||||||
CustomerEmail = invoice.RefundMail,
|
CustomerEmail = invoice.Metadata.BuyerEmail,
|
||||||
RequiresRefundEmail = invoice.RequiresRefundEmail ?? storeBlob.RequiresRefundEmail,
|
RequiresRefundEmail = invoice.RequiresRefundEmail ?? storeBlob.RequiresRefundEmail,
|
||||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||||
DisplayExpirationTimer = (int)storeBlob.DisplayExpirationTimer.TotalSeconds,
|
DisplayExpirationTimer = (int)storeBlob.DisplayExpirationTimer.TotalSeconds,
|
||||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||||
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
||||||
ItemDesc = invoice.Metadata.ItemDesc,
|
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 ?? "/",
|
MerchantRefLink = invoice.RedirectURL?.AbsoluteUri ?? receiptUrl ?? "/",
|
||||||
ReceiptLink = receiptUrl,
|
ReceiptLink = receiptUrl,
|
||||||
RedirectAutomatically = invoice.RedirectAutomatically,
|
RedirectAutomatically = invoice.RedirectAutomatically,
|
||||||
|
@ -894,48 +918,54 @@ namespace BTCPayServer.Controllers
|
||||||
SpeedPolicy.LowSpeed => 6,
|
SpeedPolicy.LowSpeed => 6,
|
||||||
_ => null
|
_ => 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
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
Status = invoice.StatusString,
|
Status = invoice.StatusString,
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
|
NetworkFee = prompt.PaymentMethodFee,
|
||||||
IsMultiCurrency = invoice.GetPayments(false).Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
IsMultiCurrency = invoice.GetPayments(false).Select(p => p.PaymentMethodId).Concat(new[] { prompt.PaymentMethodId }).Distinct().Count() > 1,
|
||||||
StoreId = store.Id,
|
StoreId = store.Id,
|
||||||
AvailableCryptos = invoice.GetPaymentMethods()
|
AvailableCryptos = invoice.GetPaymentPrompts()
|
||||||
.Select(kv =>
|
.Select(kv =>
|
||||||
{
|
{
|
||||||
var availableCryptoPaymentMethodId = kv.GetId();
|
var handler = _handlers[kv.PaymentMethodId];
|
||||||
var availableCryptoHandler = _paymentMethodHandlerDictionary[availableCryptoPaymentMethodId];
|
var pmName = GetPaymentMethodName(kv.PaymentMethodId);
|
||||||
var pmName = availableCryptoHandler.GetPaymentMethodName(availableCryptoPaymentMethodId);
|
|
||||||
return new PaymentModel.AvailableCrypto
|
return new PaymentModel.AvailableCrypto
|
||||||
{
|
{
|
||||||
Displayed = displayedPaymentMethods.Contains(kv.GetId()),
|
Displayed = displayedPaymentMethods.Contains(kv.PaymentMethodId),
|
||||||
PaymentMethodId = kv.GetId().ToString(),
|
PaymentMethodId = kv.PaymentMethodId.ToString(),
|
||||||
CryptoCode = kv.Network?.CryptoCode ?? kv.GetId().CryptoCode,
|
CryptoCode = kv.Currency,
|
||||||
PaymentMethodName = isAltcoinsBuild
|
PaymentMethodName = isAltcoinsBuild
|
||||||
? pmName
|
? pmName
|
||||||
: pmName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", ""),
|
: pmName.Replace("Bitcoin (", "").Replace(")", "").Replace("Lightning ", ""),
|
||||||
IsLightning =
|
IsLightning = handler is ILightningPaymentHandler,
|
||||||
kv.GetId().PaymentType == PaymentTypes.LightningLike,
|
CryptoImage = Request.GetRelativePathOrAbsolute(GetPaymentMethodImage(kv.PaymentMethodId)),
|
||||||
CryptoImage = Request.GetRelativePathOrAbsolute(availableCryptoHandler.GetCryptoImage(availableCryptoPaymentMethodId)),
|
|
||||||
Link = Url.Action(nameof(Checkout),
|
Link = Url.Action(nameof(Checkout),
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
invoiceId,
|
invoiceId,
|
||||||
paymentMethodId = kv.GetId().ToString()
|
paymentMethodId = kv.PaymentMethodId.ToString()
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}).Where(c => c.CryptoImage != "/")
|
}).Where(c => c.CryptoImage != "/")
|
||||||
.OrderByDescending(a => a.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode).ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
|
.OrderByDescending(a => a.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode).ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0)
|
||||||
.ToList()
|
.ToList()
|
||||||
};
|
};
|
||||||
|
if (_paymentModelExtensions.TryGetValue(paymentMethodId, out var extension))
|
||||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob, paymentMethod);
|
extension.ModifyPaymentModel(new PaymentModelContext(model, store, storeBlob, invoice, Url, prompt, handler));
|
||||||
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
model.UISettings = _viewProvider.TryGetViewViewModel(prompt, "CheckoutUI")?.View as CheckoutUIPaymentMethodSettings;
|
||||||
model.PaymentMethodId = paymentMethodId.ToString();
|
model.PaymentMethodId = paymentMethodId.ToString();
|
||||||
model.PaymentType = paymentMethodId.PaymentType.ToString();
|
|
||||||
model.OrderAmountFiat = OrderAmountFromInvoice(model.CryptoCode, invoice, DisplayFormatter.CurrencyFormat.Symbol);
|
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)
|
if (storeBlob.PlaySoundOnPayment)
|
||||||
{
|
{
|
||||||
model.PaymentSoundUrl = string.IsNullOrEmpty(storeBlob.SoundFileId)
|
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.ErrorSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout-v2/error.mp3");
|
||||||
model.NfcReadSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout-v2/nfcread.mp3");
|
model.NfcReadSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout-v2/nfcread.mp3");
|
||||||
}
|
}
|
||||||
|
|
||||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||||
model.TimeLeft = expiration.PrettyPrint();
|
model.TimeLeft = expiration.PrettyPrint();
|
||||||
return model;
|
return model;
|
||||||
|
@ -962,7 +992,7 @@ namespace BTCPayServer.Controllers
|
||||||
return _displayFormatter.Currency(invoiceEntity.Price, currency, format);
|
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 currency = paymentMethod.ParentEntity.Currency;
|
||||||
var crypto = cryptoCode.ToUpperInvariant(); // uppercase to make comparison easier, might be "sats"
|
var crypto = cryptoCode.ToUpperInvariant(); // uppercase to make comparison easier, might be "sats"
|
||||||
|
@ -1081,7 +1111,7 @@ namespace BTCPayServer.Controllers
|
||||||
invoiceQuery.Take = model.Count;
|
invoiceQuery.Take = model.Count;
|
||||||
invoiceQuery.Skip = model.Skip;
|
invoiceQuery.Skip = model.Skip;
|
||||||
invoiceQuery.IncludeRefunds = true;
|
invoiceQuery.IncludeRefunds = true;
|
||||||
|
|
||||||
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
||||||
|
|
||||||
// Apps
|
// Apps
|
||||||
|
@ -1157,12 +1187,12 @@ namespace BTCPayServer.Controllers
|
||||||
var store = await _StoreRepository.FindStore(model.StoreId);
|
var store = await _StoreRepository.FindStore(model.StoreId);
|
||||||
if (store == null)
|
if (store == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
if (!store.AnyPaymentMethodAvailable(_NetworkProvider))
|
if (!store.AnyPaymentMethodAvailable())
|
||||||
{
|
{
|
||||||
return NoPaymentMethodResult(store.Id);
|
return NoPaymentMethodResult(store.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var vm = new CreateInvoiceModel
|
var vm = new CreateInvoiceModel
|
||||||
{
|
{
|
||||||
|
@ -1182,11 +1212,11 @@ namespace BTCPayServer.Controllers
|
||||||
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken)
|
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
if (!store.AnyPaymentMethodAvailable(_NetworkProvider))
|
if (!store.AnyPaymentMethodAvailable())
|
||||||
{
|
{
|
||||||
return NoPaymentMethodResult(store.Id);
|
return NoPaymentMethodResult(store.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
model.CheckoutType = storeBlob.CheckoutType;
|
model.CheckoutType = storeBlob.CheckoutType;
|
||||||
model.AvailablePaymentMethods = GetPaymentMethodsSelectList(store);
|
model.AvailablePaymentMethods = GetPaymentMethodsSelectList(store);
|
||||||
|
@ -1203,7 +1233,7 @@ namespace BTCPayServer.Controllers
|
||||||
ModelState.AddModelError(nameof(model.Metadata), "Metadata was not valid JSON");
|
ModelState.AddModelError(nameof(model.Metadata), "Metadata was not valid JSON");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(model);
|
return View(model);
|
||||||
|
@ -1231,7 +1261,7 @@ namespace BTCPayServer.Controllers
|
||||||
Amount = model.Amount,
|
Amount = model.Amount,
|
||||||
Currency = model.Currency,
|
Currency = model.Currency,
|
||||||
Metadata = metadata.ToJObject(),
|
Metadata = metadata.ToJObject(),
|
||||||
Checkout = new ()
|
Checkout = new()
|
||||||
{
|
{
|
||||||
RedirectURL = store.StoreWebsite,
|
RedirectURL = store.StoreWebsite,
|
||||||
DefaultPaymentMethod = model.DefaultPaymentMethod,
|
DefaultPaymentMethod = model.DefaultPaymentMethod,
|
||||||
|
@ -1338,7 +1368,7 @@ namespace BTCPayServer.Controllers
|
||||||
? ParsePosData(items[i])
|
? ParsePosData(items[i])
|
||||||
: items[i].ToString());
|
: items[i].ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
result.TryAdd(item.Key, arrayResult);
|
result.TryAdd(item.Key, arrayResult);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -1357,9 +1387,9 @@ namespace BTCPayServer.Controllers
|
||||||
private SelectList GetPaymentMethodsSelectList(StoreData store)
|
private SelectList GetPaymentMethodsSelectList(StoreData store)
|
||||||
{
|
{
|
||||||
var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods();
|
var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods();
|
||||||
return new SelectList(store.GetSupportedPaymentMethods(_NetworkProvider)
|
return new SelectList(store.GetPaymentMethodConfigs()
|
||||||
.Where(s => !excludeFilter.Match(s.PaymentId))
|
.Where(s => !excludeFilter.Match(s.Key))
|
||||||
.Select(method => new SelectListItem(method.PaymentId.ToPrettyString(), method.PaymentId.ToString())),
|
.Select(method => new SelectListItem(method.Key.ToString(), method.Key.ToString())),
|
||||||
nameof(SelectListItem.Value),
|
nameof(SelectListItem.Value),
|
||||||
nameof(SelectListItem.Text));
|
nameof(SelectListItem.Text));
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ using Microsoft.AspNetCore.Routing;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using StoreData = BTCPayServer.Data.StoreData;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
|
using Serilog.Filters;
|
||||||
|
using PeterO.Numbers;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
|
@ -47,7 +49,7 @@ namespace BTCPayServer.Controllers
|
||||||
private readonly DisplayFormatter _displayFormatter;
|
private readonly DisplayFormatter _displayFormatter;
|
||||||
readonly EventAggregator _EventAggregator;
|
readonly EventAggregator _EventAggregator;
|
||||||
readonly BTCPayNetworkProvider _NetworkProvider;
|
readonly BTCPayNetworkProvider _NetworkProvider;
|
||||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
private readonly PullPaymentHostedService _paymentHostedService;
|
private readonly PullPaymentHostedService _paymentHostedService;
|
||||||
private readonly LanguageService _languageService;
|
private readonly LanguageService _languageService;
|
||||||
|
@ -57,6 +59,8 @@ namespace BTCPayServer.Controllers
|
||||||
private readonly LinkGenerator _linkGenerator;
|
private readonly LinkGenerator _linkGenerator;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||||
|
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
||||||
|
private readonly PaymentMethodViewProvider _viewProvider;
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
private readonly IFileService _fileService;
|
private readonly IFileService _fileService;
|
||||||
|
|
||||||
|
@ -85,7 +89,9 @@ namespace BTCPayServer.Controllers
|
||||||
AppService appService,
|
AppService appService,
|
||||||
IFileService fileService,
|
IFileService fileService,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
TransactionLinkProviders transactionLinkProviders)
|
TransactionLinkProviders transactionLinkProviders,
|
||||||
|
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||||
|
PaymentMethodViewProvider viewProvider)
|
||||||
{
|
{
|
||||||
_displayFormatter = displayFormatter;
|
_displayFormatter = displayFormatter;
|
||||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||||
|
@ -96,7 +102,7 @@ namespace BTCPayServer.Controllers
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_EventAggregator = eventAggregator;
|
_EventAggregator = eventAggregator;
|
||||||
_NetworkProvider = networkProvider;
|
_NetworkProvider = networkProvider;
|
||||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
_handlers = paymentMethodHandlerDictionary;
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_paymentHostedService = paymentHostedService;
|
_paymentHostedService = paymentHostedService;
|
||||||
WebhookNotificationManager = webhookNotificationManager;
|
WebhookNotificationManager = webhookNotificationManager;
|
||||||
|
@ -107,6 +113,8 @@ namespace BTCPayServer.Controllers
|
||||||
_linkGenerator = linkGenerator;
|
_linkGenerator = linkGenerator;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_transactionLinkProviders = transactionLinkProviders;
|
_transactionLinkProviders = transactionLinkProviders;
|
||||||
|
_paymentModelExtensions = paymentModelExtensions;
|
||||||
|
_viewProvider = viewProvider;
|
||||||
_fileService = fileService;
|
_fileService = fileService;
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +134,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(id),
|
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(id),
|
||||||
PaymentRequestId = 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
|
string.IsNullOrEmpty(prBlob.Email) ? null : prBlob.Email
|
||||||
}.ToJObject(), new JsonMergeSettings() { MergeNullValueHandling = MergeNullValueHandling.Ignore });
|
}.ToJObject(), new JsonMergeSettings() { MergeNullValueHandling = MergeNullValueHandling.Ignore });
|
||||||
|
|
||||||
|
@ -169,7 +177,10 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy;
|
entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy;
|
||||||
entity.DefaultLanguage = invoice.Checkout.DefaultLanguage;
|
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.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
|
||||||
entity.CheckoutType = invoice.Checkout.CheckoutType;
|
entity.CheckoutType = invoice.Checkout.CheckoutType;
|
||||||
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
|
entity.RequiresRefundEmail = invoice.Checkout.RequiresRefundEmail;
|
||||||
|
@ -217,77 +228,29 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
var getAppsTaggingStore = _InvoiceRepository.GetAppsTaggingStore(store.Id);
|
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.Status = InvoiceStatusLegacy.New;
|
||||||
entity.UpdateTotals();
|
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,
|
await creationContext.BeforeFetchingRates();
|
||||||
invoicePaymentMethodFilter);
|
await FetchRates(creationContext, cancellationToken);
|
||||||
}
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var rateRules = storeBlob.GetRateRules(_NetworkProvider);
|
await creationContext.CreatePaymentPrompts();
|
||||||
var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules, cancellationToken);
|
var contexts = creationContext.PaymentMethodContexts
|
||||||
var fetchingAll = WhenAllFetched(logs, fetchingByCurrencyPair);
|
.Where(s => s.Value.Status is PaymentMethodContext.ContextStatus.WaitingForActivation or PaymentMethodContext.ContextStatus.Created)
|
||||||
|
.Select(s => s.Value)
|
||||||
List<ISupportedPaymentMethod> supported = new List<ISupportedPaymentMethod>();
|
.ToList();
|
||||||
var paymentMethods = new PaymentMethodDictionary();
|
if (contexts.Count == 0)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
StringBuilder errors = new StringBuilder();
|
StringBuilder errors = new StringBuilder();
|
||||||
if (!store.GetSupportedPaymentMethods(_NetworkProvider).Any())
|
if (!store.GetPaymentMethodConfigs(_handlers).Any())
|
||||||
errors.AppendLine(
|
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/)");
|
"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
|
else
|
||||||
|
@ -299,9 +262,13 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
throw new BitpayHttpException(400, errors.ToString());
|
throw new BitpayHttpException(400, errors.ToString());
|
||||||
}
|
}
|
||||||
|
entity.SetPaymentPrompts(new PaymentPromptDictionary(contexts.Select(c => c.Prompt)));
|
||||||
}
|
}
|
||||||
entity.SetSupportedPaymentMethods(supported);
|
else
|
||||||
entity.SetPaymentMethods(paymentMethods);
|
{
|
||||||
|
entity.SetPaymentPrompts(new PaymentPromptDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var app in await getAppsTaggingStore)
|
foreach (var app in await getAppsTaggingStore)
|
||||||
{
|
{
|
||||||
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
|
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
|
||||||
|
@ -313,151 +280,18 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
using (logs.Measure("Saving invoice"))
|
using (logs.Measure("Saving invoice"))
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.CreateInvoiceAsync(entity, additionalSearchTerms);
|
await _InvoiceRepository.CreateInvoiceAsync(creationContext);
|
||||||
var links = new List<WalletObjectLinkData>();
|
await creationContext.ActivatingPaymentPrompt();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
_ = Task.Run(async () =>
|
_ = _InvoiceRepository.AddInvoiceLogs(entity.Id, logs);
|
||||||
{
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
_EventAggregator.Publish(new Events.InvoiceEvent(entity, InvoiceEvent.Created));
|
_EventAggregator.Publish(new Events.InvoiceEvent(entity, InvoiceEvent.Created));
|
||||||
return entity;
|
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 rateRules = context.StoreBlob.GetRateRules(_NetworkProvider);
|
||||||
{
|
await context.FetchingRates(_RateProvider, rateRules, cancellationToken);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using LightningAddressData = BTCPayServer.Data.LightningAddressData;
|
using LightningAddressData = BTCPayServer.Data.LightningAddressData;
|
||||||
|
@ -47,8 +48,6 @@ namespace BTCPayServer
|
||||||
{
|
{
|
||||||
private readonly InvoiceRepository _invoiceRepository;
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
||||||
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
private readonly UIInvoiceController _invoiceController;
|
private readonly UIInvoiceController _invoiceController;
|
||||||
|
@ -59,11 +58,11 @@ namespace BTCPayServer
|
||||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||||
private readonly IPluginHookService _pluginHookService;
|
private readonly IPluginHookService _pluginHookService;
|
||||||
private readonly InvoiceActivator _invoiceActivator;
|
private readonly InvoiceActivator _invoiceActivator;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
|
||||||
public UILNURLController(InvoiceRepository invoiceRepository,
|
public UILNURLController(InvoiceRepository invoiceRepository,
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
PaymentMethodHandlerDictionary handlers,
|
||||||
LightningLikePaymentHandler lightningLikePaymentHandler,
|
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
AppService appService,
|
AppService appService,
|
||||||
UIInvoiceController invoiceController,
|
UIInvoiceController invoiceController,
|
||||||
|
@ -77,8 +76,7 @@ namespace BTCPayServer
|
||||||
{
|
{
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
_handlers = handlers;
|
||||||
_lightningLikePaymentHandler = lightningLikePaymentHandler;
|
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
_invoiceController = invoiceController;
|
_invoiceController = invoiceController;
|
||||||
|
@ -98,13 +96,13 @@ namespace BTCPayServer
|
||||||
[NonAction]
|
[NonAction]
|
||||||
internal async Task<IActionResult> GetLNURLForPullPayment(string cryptoCode, string pullPaymentId, string pr, string k1, CancellationToken cancellationToken)
|
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)
|
if (network is null || !network.SupportLightning)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||||
var pp = await _pullPaymentHostedService.GetPullPayment(pullPaymentId, true);
|
var pp = await _pullPaymentHostedService.GetPullPayment(pullPaymentId, true);
|
||||||
if (!pp.IsRunning() || !pp.IsSupported(pmi))
|
if (!pp.IsRunning() || !pp.IsSupported(pmi))
|
||||||
{
|
{
|
||||||
|
@ -149,9 +147,7 @@ namespace BTCPayServer
|
||||||
if (result.MinimumAmount < request.MinWithdrawable || result.MinimumAmount > request.MaxWithdrawable)
|
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)" });
|
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 store = await _storeRepository.FindStore(pp.StoreId);
|
||||||
var pm = store!.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
var pm = store!.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _handlers);
|
||||||
.OfType<LightningSupportedPaymentMethod>()
|
|
||||||
.FirstOrDefault(method => method.PaymentId == pmi);
|
|
||||||
if (pm is null)
|
if (pm is null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
@ -169,12 +165,13 @@ namespace BTCPayServer
|
||||||
if (claimResponse.Result != ClaimRequest.ClaimResult.Ok)
|
if (claimResponse.Result != ClaimRequest.ClaimResult.Ok)
|
||||||
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Payment request could not be paid" });
|
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Payment request could not be paid" });
|
||||||
|
|
||||||
|
var lightningHandler = _handlers.GetLightningHandler(network);
|
||||||
switch (claimResponse.PayoutData.State)
|
switch (claimResponse.PayoutData.State)
|
||||||
{
|
{
|
||||||
case PayoutState.AwaitingPayment:
|
case PayoutState.AwaitingPayment:
|
||||||
{
|
{
|
||||||
var client =
|
var client =
|
||||||
_lightningLikePaymentHandler.CreateLightningClient(pm, network);
|
lightningHandler.CreateLightningClient(pm);
|
||||||
var payResult = await UILightningLikePayoutController.TrypayBolt(client,
|
var payResult = await UILightningLikePayoutController.TrypayBolt(client,
|
||||||
claimResponse.PayoutData.GetBlob(_btcPayNetworkJsonSerializerSettings),
|
claimResponse.PayoutData.GetBlob(_btcPayNetworkJsonSerializerSettings),
|
||||||
claimResponse.PayoutData, result, pmi, cancellationToken);
|
claimResponse.PayoutData, result, pmi, cancellationToken);
|
||||||
|
@ -226,10 +223,18 @@ namespace BTCPayServer
|
||||||
return Ok(request);
|
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}")]
|
[HttpGet("pay/app/{appId}/{itemCode}")]
|
||||||
public async Task<IActionResult> GetLNURLForApp(string cryptoCode, string appId, string itemCode = null)
|
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)
|
if (network is null || !network.SupportLightning)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
@ -514,7 +519,7 @@ namespace BTCPayServer
|
||||||
{
|
{
|
||||||
createInvoice.Checkout ??= new InvoiceDataBase.CheckoutOptions();
|
createInvoice.Checkout ??= new InvoiceDataBase.CheckoutOptions();
|
||||||
createInvoice.Checkout.LazyPaymentMethods = false;
|
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);
|
i = await _invoiceController.CreateInvoiceCoreRaw(createInvoice, store, Request.GetAbsoluteRoot(), additionalTags);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -540,14 +545,17 @@ namespace BTCPayServer
|
||||||
lnurlRequest ??= new LNURLPayRequest();
|
lnurlRequest ??= new LNURLPayRequest();
|
||||||
lnUrlMetadata ??= new Dictionary<string, string>();
|
lnUrlMetadata ??= new Dictionary<string, string>();
|
||||||
|
|
||||||
var pm = i.GetPaymentMethod(pmi);
|
var pm = i.GetPaymentPrompt(pmi);
|
||||||
if (pm is null)
|
if (pm is null)
|
||||||
return null;
|
return null;
|
||||||
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
|
var handler = ((LNURLPayPaymentHandler)_handlers[pmi]);
|
||||||
|
var paymentMethodDetails = handler.ParsePaymentPromptDetails(pm.Details);
|
||||||
bool updatePaymentMethodDetails = false;
|
bool updatePaymentMethodDetails = false;
|
||||||
|
List<string> searchTerms = new List<string>();
|
||||||
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is not null)
|
if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is not null)
|
||||||
{
|
{
|
||||||
paymentMethodDetails.ConsumedLightningAddress = lnAddress;
|
paymentMethodDetails.ConsumedLightningAddress = lnAddress;
|
||||||
|
searchTerms.Add(lnAddress);
|
||||||
updatePaymentMethodDetails = true;
|
updatePaymentMethodDetails = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,7 +569,7 @@ namespace BTCPayServer
|
||||||
lnurlRequest.Callback = new Uri(_linkGenerator.GetUriByAction(
|
lnurlRequest.Callback = new Uri(_linkGenerator.GetUriByAction(
|
||||||
action: nameof(GetLNURLForInvoice),
|
action: nameof(GetLNURLForInvoice),
|
||||||
controller: "UILNURL",
|
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 }));
|
lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
|
||||||
if (i.Type != InvoiceType.TopUp)
|
if (i.Type != InvoiceType.TopUp)
|
||||||
{
|
{
|
||||||
|
@ -580,8 +588,10 @@ namespace BTCPayServer
|
||||||
}
|
}
|
||||||
if (updatePaymentMethodDetails)
|
if (updatePaymentMethodDetails)
|
||||||
{
|
{
|
||||||
pm.SetPaymentMethodDetails(paymentMethodDetails);
|
pm.Details = JToken.FromObject(paymentMethodDetails, handler.Serializer);
|
||||||
await _invoiceRepository.UpdateInvoicePaymentMethod(i.Id, pm);
|
await _invoiceRepository.UpdatePaymentDetails(i.Id, handler, paymentMethodDetails);
|
||||||
|
await _invoiceRepository.AddSearchTerms(i.Id, searchTerms);
|
||||||
|
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(i.Id, paymentMethodDetails, pmi));
|
||||||
}
|
}
|
||||||
return lnurlRequest;
|
return lnurlRequest;
|
||||||
}
|
}
|
||||||
|
@ -605,18 +615,16 @@ namespace BTCPayServer
|
||||||
lnurlRequest.MaxSendable = LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC);
|
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;
|
lnUrlSettings = null;
|
||||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
var network = GetNetwork(cryptoCode);
|
||||||
if (network is null || !network.SupportLightning)
|
if (network is null || !network.SupportLightning)
|
||||||
return null;
|
return null;
|
||||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
var pmi = PaymentTypes.LNURL.GetPaymentMethodId(cryptoCode);
|
||||||
var lnpmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
var lnpmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||||
var methods = store.GetSupportedPaymentMethods(_btcPayNetworkProvider);
|
var lnUrlMethod = store.GetPaymentMethodConfig<LNURLPaymentMethodConfig>(pmi, _handlers);
|
||||||
var lnUrlMethod =
|
var lnMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(lnpmi, _handlers);
|
||||||
methods.FirstOrDefault(method => method.PaymentId == pmi) as LNURLPaySupportedPaymentMethod;
|
|
||||||
var lnMethod = methods.FirstOrDefault(method => method.PaymentId == lnpmi);
|
|
||||||
if (lnUrlMethod is null || lnMethod is null)
|
if (lnUrlMethod is null || lnMethod is null)
|
||||||
return null;
|
return null;
|
||||||
var blob = store.GetStoreBlob();
|
var blob = store.GetStoreBlob();
|
||||||
|
@ -632,7 +640,7 @@ namespace BTCPayServer
|
||||||
public async Task<IActionResult> GetLNURLForInvoice(string invoiceId, string cryptoCode,
|
public async Task<IActionResult> GetLNURLForInvoice(string invoiceId, string cryptoCode,
|
||||||
[FromQuery] long? amount = null, string comment = null)
|
[FromQuery] long? amount = null, string comment = null)
|
||||||
{
|
{
|
||||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
var network = GetNetwork(cryptoCode);
|
||||||
if (network is null || !network.SupportLightning)
|
if (network is null || !network.SupportLightning)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
@ -651,25 +659,25 @@ namespace BTCPayServer
|
||||||
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out var lnurlSupportedPaymentMethod);
|
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out var lnurlSupportedPaymentMethod);
|
||||||
if (pmi is null)
|
if (pmi is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
var handler = ((LNURLPayPaymentHandler)_handlers[pmi]);
|
||||||
var lightningPaymentMethod = i.GetPaymentMethod(pmi);
|
var lightningPaymentMethod = i.GetPaymentPrompt(pmi);
|
||||||
var paymentMethodDetails =
|
var promptDetails = handler.ParsePaymentPromptDetails(lightningPaymentMethod.Details);
|
||||||
lightningPaymentMethod?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
if (promptDetails is null)
|
||||||
if (paymentMethodDetails is not null && !paymentMethodDetails.Activated)
|
|
||||||
{
|
{
|
||||||
if (!await _invoiceActivator.ActivateInvoicePaymentMethod(pmi, i, store))
|
if (!await _invoiceActivator.ActivateInvoicePaymentMethod(i.Id, pmi))
|
||||||
return NotFound();
|
return NotFound();
|
||||||
i = await _invoiceRepository.GetInvoice(invoiceId, true);
|
i = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
lightningPaymentMethod = i.GetPaymentMethod(pmi);
|
lightningPaymentMethod = i.GetPaymentPrompt(pmi);
|
||||||
paymentMethodDetails = lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
promptDetails = handler.ParsePaymentPromptDetails(lightningPaymentMethod.Details);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paymentMethodDetails?.LightningSupportedPaymentMethod is null)
|
var lnConfig = _handlers.GetLightningConfig(store, network);
|
||||||
|
if (lnConfig is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
LNURLPayRequest lnurlPayRequest = paymentMethodDetails.PayRequest;
|
LNURLPayRequest lnurlPayRequest = promptDetails.PayRequest;
|
||||||
var blob = store.GetStoreBlob();
|
var blob = store.GetStoreBlob();
|
||||||
if (paymentMethodDetails.PayRequest is null)
|
if (promptDetails.PayRequest is null)
|
||||||
{
|
{
|
||||||
lnurlPayRequest = await CreateLNUrlRequestFromInvoice(cryptoCode, i, store, blob, allowOverpay: false);
|
lnurlPayRequest = await CreateLNUrlRequestFromInvoice(cryptoCode, i, store, blob, allowOverpay: false);
|
||||||
if (lnurlPayRequest is null)
|
if (lnurlPayRequest is null)
|
||||||
|
@ -705,23 +713,20 @@ namespace BTCPayServer
|
||||||
if (lnurlSupportedPaymentMethod.LUD12Enabled)
|
if (lnurlSupportedPaymentMethod.LUD12Enabled)
|
||||||
{
|
{
|
||||||
comment = comment?.Truncate(2000);
|
comment = comment?.Truncate(2000);
|
||||||
if (paymentMethodDetails.ProvidedComment != comment)
|
if (promptDetails.ProvidedComment != comment)
|
||||||
{
|
{
|
||||||
paymentMethodDetails.ProvidedComment = comment;
|
promptDetails.ProvidedComment = comment;
|
||||||
updatePaymentMethod = true;
|
updatePaymentMethod = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (string.IsNullOrEmpty(lightningPaymentMethod.Destination) || promptDetails.GeneratedBoltAmount != amt)
|
||||||
if (string.IsNullOrEmpty(paymentMethodDetails.BOLT11) || paymentMethodDetails.GeneratedBoltAmount != amt)
|
|
||||||
{
|
{
|
||||||
var client =
|
var client = _handlers.GetLightningHandler(network).CreateLightningClient(lnConfig);
|
||||||
_lightningLikePaymentHandler.CreateLightningClient(
|
if (!string.IsNullOrEmpty(lightningPaymentMethod.Destination))
|
||||||
paymentMethodDetails.LightningSupportedPaymentMethod, network);
|
|
||||||
if (!string.IsNullOrEmpty(paymentMethodDetails.BOLT11))
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await client.CancelInvoice(paymentMethodDetails.InvoiceId);
|
await client.CancelInvoice(promptDetails.InvoiceId);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
@ -764,26 +769,26 @@ namespace BTCPayServer
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
paymentMethodDetails.BOLT11 = invoice.BOLT11;
|
lightningPaymentMethod.Destination = invoice.BOLT11;
|
||||||
paymentMethodDetails.PaymentHash = string.IsNullOrEmpty(invoice.PaymentHash) ? null : uint256.Parse(invoice.PaymentHash);
|
promptDetails.PaymentHash = string.IsNullOrEmpty(invoice.PaymentHash) ? null : uint256.Parse(invoice.PaymentHash);
|
||||||
paymentMethodDetails.Preimage = string.IsNullOrEmpty(invoice.Preimage) ? null : uint256.Parse(invoice.Preimage);
|
promptDetails.Preimage = string.IsNullOrEmpty(invoice.Preimage) ? null : uint256.Parse(invoice.Preimage);
|
||||||
paymentMethodDetails.InvoiceId = invoice.Id;
|
promptDetails.InvoiceId = invoice.Id;
|
||||||
paymentMethodDetails.GeneratedBoltAmount = amt;
|
promptDetails.GeneratedBoltAmount = amt;
|
||||||
|
lightningPaymentMethod.Details = JToken.FromObject(promptDetails, handler.Serializer);
|
||||||
updatePaymentMethod = true;
|
updatePaymentMethod = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatePaymentMethod)
|
if (updatePaymentMethod)
|
||||||
{
|
{
|
||||||
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
await _invoiceRepository.UpdatePrompt(invoiceId, lightningPaymentMethod);
|
||||||
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
|
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId, promptDetails, pmi));
|
||||||
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId, paymentMethodDetails, pmi));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||||
{
|
{
|
||||||
Disposable = true,
|
Disposable = true,
|
||||||
Routes = Array.Empty<string>(),
|
Routes = Array.Empty<string>(),
|
||||||
Pr = paymentMethodDetails.BOLT11,
|
Pr = lightningPaymentMethod.Destination,
|
||||||
SuccessAction = successAction
|
SuccessAction = successAction
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -800,8 +805,8 @@ namespace BTCPayServer
|
||||||
[HttpGet("~/stores/{storeId}/plugins/lightning-address")]
|
[HttpGet("~/stores/{storeId}/plugins/lightning-address")]
|
||||||
public async Task<IActionResult> EditLightningAddress(string storeId)
|
public async Task<IActionResult> EditLightningAddress(string storeId)
|
||||||
{
|
{
|
||||||
if (ControllerContext.HttpContext.GetStoreData().GetEnabledPaymentIds(_btcPayNetworkProvider)
|
if (ControllerContext.HttpContext.GetStoreData().GetEnabledPaymentIds()
|
||||||
.All(id => id.PaymentType != LNURLPayPaymentType.Instance))
|
.All(id => _handlers.TryGet(id) is not LNURLPayPaymentHandler))
|
||||||
{
|
{
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
{
|
{
|
||||||
|
|
|
@ -121,7 +121,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
if (!store.AnyPaymentMethodAvailable(_networkProvider))
|
if (!store.AnyPaymentMethodAvailable())
|
||||||
{
|
{
|
||||||
return NoPaymentMethodResult(storeId);
|
return NoPaymentMethodResult(storeId);
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
if (!store.AnyPaymentMethodAvailable(_networkProvider))
|
if (!store.AnyPaymentMethodAvailable())
|
||||||
{
|
{
|
||||||
return NoPaymentMethodResult(store.Id);
|
return NoPaymentMethodResult(store.Id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
@ -8,6 +9,7 @@ using BTCPayServer.Logging;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -19,13 +21,19 @@ namespace BTCPayServer.Controllers
|
||||||
public class UIPublicLightningNodeInfoController : Controller
|
public class UIPublicLightningNodeInfoController : Controller
|
||||||
{
|
{
|
||||||
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider;
|
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider;
|
||||||
|
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly LightningLikePaymentHandler _LightningLikePaymentHandler;
|
private readonly LightningLikePaymentHandler _LightningLikePaymentHandler;
|
||||||
private readonly StoreRepository _StoreRepository;
|
private readonly StoreRepository _StoreRepository;
|
||||||
|
|
||||||
public UIPublicLightningNodeInfoController(BTCPayNetworkProvider btcPayNetworkProvider,
|
public UIPublicLightningNodeInfoController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
|
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||||
|
PaymentMethodHandlerDictionary handlers,
|
||||||
LightningLikePaymentHandler lightningLikePaymentHandler, StoreRepository storeRepository)
|
LightningLikePaymentHandler lightningLikePaymentHandler, StoreRepository storeRepository)
|
||||||
{
|
{
|
||||||
_BtcPayNetworkProvider = btcPayNetworkProvider;
|
_BtcPayNetworkProvider = btcPayNetworkProvider;
|
||||||
|
_paymentModelExtensions = paymentModelExtensions;
|
||||||
|
_handlers = handlers;
|
||||||
_LightningLikePaymentHandler = lightningLikePaymentHandler;
|
_LightningLikePaymentHandler = lightningLikePaymentHandler;
|
||||||
_StoreRepository = storeRepository;
|
_StoreRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
@ -47,13 +55,13 @@ namespace BTCPayServer.Controllers
|
||||||
};
|
};
|
||||||
try
|
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 network = _BtcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||||
var nodeInfo = await _LightningLikePaymentHandler.GetNodeInfo(paymentMethodDetails, network,
|
var nodeInfo = await _LightningLikePaymentHandler.GetNodeInfo(paymentMethodDetails, null, throws: true);
|
||||||
new InvoiceLogs(), throws: true);
|
|
||||||
|
|
||||||
vm.Available = true;
|
vm.Available = true;
|
||||||
vm.CryptoImage = GetImage(paymentMethodDetails.PaymentId, network);
|
vm.CryptoImage = GetImage(pmi);
|
||||||
vm.NodeInfo = nodeInfo.Select(n => new ShowLightningNodeInfoViewModel.NodeData(n)).ToArray();
|
vm.NodeInfo = nodeInfo.Select(n => new ShowLightningNodeInfoViewModel.NodeData(n)).ToArray();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
|
@ -64,21 +72,13 @@ namespace BTCPayServer.Controllers
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
private string GetImage(PaymentMethodId paymentMethodId)
|
||||||
{
|
{
|
||||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
if (_paymentModelExtensions.TryGetValue(paymentMethodId, out var paymentModelExtension))
|
||||||
var existing = store.GetSupportedPaymentMethods(_BtcPayNetworkProvider)
|
{
|
||||||
.OfType<LightningSupportedPaymentMethod>()
|
return "/" + Url.Content(paymentModelExtension.Image);
|
||||||
.FirstOrDefault(d => d.PaymentId == id);
|
}
|
||||||
return existing;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
|
|
||||||
{
|
|
||||||
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
|
|
||||||
? Url.Content(network.CryptoImagePath)
|
|
||||||
: Url.Content(network.LightningImagePath);
|
|
||||||
return "/" + res;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ public partial class UIReportsController : Controller
|
||||||
var vm = new StoreReportsViewModel
|
var vm = new StoreReportsViewModel
|
||||||
{
|
{
|
||||||
InvoiceTemplateUrl = Url.Action(nameof(UIInvoiceController.Invoice), "UIInvoice", new { invoiceId = "INVOICE_ID" }),
|
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" },
|
Request = new StoreReportRequest { ViewName = viewName ?? "Payments" },
|
||||||
AvailableViews = ReportService.ReportProviders
|
AvailableViews = ReportService.ReportProviders
|
||||||
.Values
|
.Values
|
||||||
|
|
|
@ -348,7 +348,7 @@ namespace BTCPayServer.Controllers
|
||||||
return View(settings);
|
return View(settings);
|
||||||
}
|
}
|
||||||
settings.BlockExplorerLinks = settings.BlockExplorerLinks
|
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)
|
.Where(tuple => tuple.Link is not null)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ namespace BTCPayServer.Controllers
|
||||||
CustomCSSLink = "",
|
CustomCSSLink = "",
|
||||||
EmbeddedCSS = "",
|
EmbeddedCSS = "",
|
||||||
PaymentMethodItems =
|
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);
|
var paymentMethodOptions = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
|
||||||
model.PaymentMethodItems =
|
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.Name ??= string.Empty;
|
||||||
model.Currency = model.Currency?.ToUpperInvariant()?.Trim() ?? String.Empty;
|
model.Currency = model.Currency?.ToUpperInvariant()?.Trim() ?? String.Empty;
|
||||||
model.PaymentMethods ??= new List<string>();
|
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
|
// 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
|
// them here to reflect user's selection so that they can correct their mistake
|
||||||
model.PaymentMethodItems =
|
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");
|
ModelState.AddModelError(nameof(model.PaymentMethods), "You need at least one payment method");
|
||||||
}
|
}
|
||||||
if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null)
|
if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null)
|
||||||
|
@ -386,7 +386,7 @@ namespace BTCPayServer.Controllers
|
||||||
case "pay":
|
case "pay":
|
||||||
{
|
{
|
||||||
if (handler is { })
|
if (handler is { })
|
||||||
return await handler?.InitiatePayment(paymentMethodId, payoutIds);
|
return await handler.InitiatePayment(paymentMethodId, payoutIds);
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
{
|
{
|
||||||
Message = "Paying via this payment method is not supported",
|
Message = "Paying via this payment method is not supported",
|
||||||
|
|
|
@ -14,9 +14,12 @@ using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
|
@ -110,6 +113,9 @@ namespace BTCPayServer.Controllers
|
||||||
if (store == null)
|
if (store == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
var network = _ExplorerProvider.GetNetwork(vm.CryptoCode);
|
||||||
|
var oldConf = _handlers.GetLightningConfig(store, network);
|
||||||
|
|
||||||
vm.CanUseInternalNode = CanUseInternalLightning(vm.CryptoCode);
|
vm.CanUseInternalNode = CanUseInternalLightning(vm.CryptoCode);
|
||||||
|
|
||||||
if (vm.CryptoCode == null)
|
if (vm.CryptoCode == null)
|
||||||
|
@ -118,21 +124,13 @@ namespace BTCPayServer.Controllers
|
||||||
return View(vm);
|
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 (vm.LightningNodeType == LightningNodeType.Internal)
|
||||||
{
|
{
|
||||||
if (!CanUseInternalLightning(network.CryptoCode))
|
paymentMethod = new LightningPaymentMethodConfig();
|
||||||
{
|
|
||||||
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.SetInternalNode();
|
paymentMethod.SetInternalNode();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -142,47 +140,26 @@ namespace BTCPayServer.Controllers
|
||||||
ModelState.AddModelError(nameof(vm.ConnectionString), "Please provide a connection string");
|
ModelState.AddModelError(nameof(vm.ConnectionString), "Please provide a connection string");
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
paymentMethod = new LightningPaymentMethodConfig();
|
||||||
ILightningClient? lightningClient = null;
|
paymentMethod.ConnectionString = vm.ConnectionString;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
switch (command)
|
||||||
{
|
{
|
||||||
case "save":
|
case "save":
|
||||||
var lnurl = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
|
var lnurl = PaymentTypes.LNURL.GetPaymentMethodId(vm.CryptoCode);
|
||||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
store.SetPaymentMethodConfig(_handlers[paymentMethodId], paymentMethod);
|
||||||
store.SetSupportedPaymentMethod(lnurl, new LNURLPaySupportedPaymentMethod()
|
store.SetPaymentMethodConfig(_handlers[lnurl], new LNURLPaymentMethodConfig()
|
||||||
{
|
{
|
||||||
CryptoCode = vm.CryptoCode,
|
|
||||||
UseBech32Scheme = true,
|
UseBech32Scheme = true,
|
||||||
LUD12Enabled = false
|
LUD12Enabled = false
|
||||||
});
|
});
|
||||||
|
@ -192,10 +169,9 @@ namespace BTCPayServer.Controllers
|
||||||
return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode });
|
return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode });
|
||||||
|
|
||||||
case "test":
|
case "test":
|
||||||
var handler = _ServiceProvider.GetRequiredService<LightningLikePaymentHandler>();
|
|
||||||
try
|
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();
|
var hasPublicAddress = info.Any();
|
||||||
if (!vm.SkipPortTest && hasPublicAddress)
|
if (!vm.SkipPortTest && hasPublicAddress)
|
||||||
{
|
{
|
||||||
|
@ -228,7 +204,8 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||||
var lightning = GetExistingLightningSupportedPaymentMethod(cryptoCode, store);
|
var lnId = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||||
|
var lightning = GetConfig<LightningPaymentMethodConfig>(lnId, store);
|
||||||
if (lightning == null)
|
if (lightning == null)
|
||||||
{
|
{
|
||||||
TempData[WellKnownTempData.ErrorMessage] = "You need to connect to a Lightning node before adjusting its settings.";
|
TempData[WellKnownTempData.ErrorMessage] = "You need to connect to a Lightning node before adjusting its settings.";
|
||||||
|
@ -240,7 +217,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
CryptoCode = cryptoCode,
|
CryptoCode = cryptoCode,
|
||||||
StoreId = storeId,
|
StoreId = storeId,
|
||||||
Enabled = !excludeFilters.Match(lightning.PaymentId),
|
Enabled = !excludeFilters.Match(lnId),
|
||||||
LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate,
|
LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate,
|
||||||
LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi,
|
LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi,
|
||||||
LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints,
|
LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints,
|
||||||
|
@ -248,10 +225,11 @@ namespace BTCPayServer.Controllers
|
||||||
};
|
};
|
||||||
SetExistingValues(store, vm);
|
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)
|
if (lnurl != null)
|
||||||
{
|
{
|
||||||
vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurl.PaymentId);
|
vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurlId);
|
||||||
vm.LNURLBech32Mode = lnurl.UseBech32Scheme;
|
vm.LNURLBech32Mode = lnurl.UseBech32Scheme;
|
||||||
vm.LUD12Enabled = lnurl.LUD12Enabled;
|
vm.LUD12Enabled = lnurl.LUD12Enabled;
|
||||||
}
|
}
|
||||||
|
@ -280,10 +258,10 @@ namespace BTCPayServer.Controllers
|
||||||
blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi;
|
blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi;
|
||||||
blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints;
|
blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints;
|
||||||
blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback;
|
blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback;
|
||||||
var lnurlId = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
|
var lnurlId = PaymentTypes.LNURL.GetPaymentMethodId(vm.CryptoCode);
|
||||||
blob.SetExcluded(lnurlId, !vm.LNURLEnabled);
|
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 || (
|
if (lnurl is null || (
|
||||||
lnurl.UseBech32Scheme != vm.LNURLBech32Mode ||
|
lnurl.UseBech32Scheme != vm.LNURLBech32Mode ||
|
||||||
lnurl.LUD12Enabled != vm.LUD12Enabled))
|
lnurl.LUD12Enabled != vm.LUD12Enabled))
|
||||||
|
@ -291,9 +269,8 @@ namespace BTCPayServer.Controllers
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.SetSupportedPaymentMethod(new LNURLPaySupportedPaymentMethod
|
store.SetPaymentMethodConfig(_handlers[lnurlId], new LNURLPaymentMethodConfig
|
||||||
{
|
{
|
||||||
CryptoCode = vm.CryptoCode,
|
|
||||||
UseBech32Scheme = vm.LNURLBech32Mode,
|
UseBech32Scheme = vm.LNURLBech32Mode,
|
||||||
LUD12Enabled = vm.LUD12Enabled
|
LUD12Enabled = vm.LUD12Enabled
|
||||||
});
|
});
|
||||||
|
@ -325,16 +302,16 @@ namespace BTCPayServer.Controllers
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var network = _ExplorerProvider.GetNetwork(cryptoCode);
|
var network = _ExplorerProvider.GetNetwork(cryptoCode);
|
||||||
var lightning = GetExistingLightningSupportedPaymentMethod(cryptoCode, store);
|
var lightning = GetConfig<LightningPaymentMethodConfig>(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), store);
|
||||||
if (lightning == null)
|
if (lightning == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
var paymentMethodId = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
storeBlob.SetExcluded(paymentMethodId, !enabled);
|
storeBlob.SetExcluded(paymentMethodId, !enabled);
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
{
|
{
|
||||||
storeBlob.SetExcluded(new PaymentMethodId(network.CryptoCode, PaymentTypes.LNURLPay), true);
|
storeBlob.SetExcluded(PaymentTypes.LNURL.GetPaymentMethodId(network.CryptoCode), true);
|
||||||
}
|
}
|
||||||
store.SetStoreBlob(storeBlob);
|
store.SetStoreBlob(storeBlob);
|
||||||
await _Repo.UpdateStore(store);
|
await _Repo.UpdateStore(store);
|
||||||
|
@ -351,7 +328,7 @@ namespace BTCPayServer.Controllers
|
||||||
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
||||||
{
|
{
|
||||||
vm.CanUseInternalNode = CanUseInternalLightning(vm.CryptoCode);
|
vm.CanUseInternalNode = CanUseInternalLightning(vm.CryptoCode);
|
||||||
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
|
var lightning = GetConfig<LightningPaymentMethodConfig>(PaymentTypes.LN.GetPaymentMethodId(vm.CryptoCode), store);
|
||||||
|
|
||||||
if (lightning != null)
|
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);
|
return store.GetPaymentMethodConfig<T>(paymentMethodId, _handlers);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,10 @@ using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
|
using BTCPayServer.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
@ -20,6 +23,7 @@ using NBitcoin.DataEncoders;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
|
@ -83,7 +87,8 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
vm.Network = network;
|
vm.Network = network;
|
||||||
DerivationSchemeSettings strategy = null;
|
DerivationSchemeSettings strategy = null;
|
||||||
|
PaymentMethodId paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||||
|
BitcoinLikePaymentHandler handler = (BitcoinLikePaymentHandler)_handlers[paymentMethodId];
|
||||||
var wallet = _WalletProvider.GetWallet(network);
|
var wallet = _WalletProvider.GetWallet(network);
|
||||||
if (wallet == null)
|
if (wallet == null)
|
||||||
{
|
{
|
||||||
|
@ -145,7 +150,11 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(vm.Config))
|
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");
|
ModelState.AddModelError(nameof(vm.Config), "Config file was not in the correct format");
|
||||||
return View(vm.ViewName, vm);
|
return View(vm.ViewName, vm);
|
||||||
|
@ -158,17 +167,16 @@ namespace BTCPayServer.Controllers
|
||||||
return View(vm.ViewName, vm);
|
return View(vm.ViewName, vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.Config = ProtectString(strategy.ToJson());
|
vm.Config = ProtectString(JToken.FromObject(strategy, handler.Serializer).ToString());
|
||||||
ModelState.Remove(nameof(vm.Config));
|
ModelState.Remove(nameof(vm.Config));
|
||||||
|
|
||||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
if (vm.Confirmation)
|
if (vm.Confirmation)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await wallet.TrackAsync(strategy.AccountDerivation);
|
await wallet.TrackAsync(strategy.AccountDerivation);
|
||||||
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
|
store.SetPaymentMethodConfig(_handlers[paymentMethodId], strategy);
|
||||||
storeBlob.SetExcluded(paymentMethodId, false);
|
storeBlob.SetExcluded(paymentMethodId, false);
|
||||||
storeBlob.PayJoinEnabled = strategy.IsHotWallet && !(vm.SetupRequest?.PayJoinEnabled is false);
|
storeBlob.PayJoinEnabled = strategy.IsHotWallet && !(vm.SetupRequest?.PayJoinEnabled is false);
|
||||||
store.SetStoreBlob(storeBlob);
|
store.SetStoreBlob(storeBlob);
|
||||||
|
@ -186,7 +194,7 @@ namespace BTCPayServer.Controllers
|
||||||
// This is success case when derivation scheme is added to the store
|
// This is success case when derivation scheme is added to the store
|
||||||
return RedirectToAction(nameof(WalletSettings), new { storeId = vm.StoreId, cryptoCode = vm.CryptoCode });
|
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)
|
private string ProtectString(string str)
|
||||||
|
@ -256,7 +264,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
var handler = _handlers.GetBitcoinHandler(cryptoCode);
|
||||||
var client = _ExplorerProvider.GetExplorerClient(cryptoCode);
|
var client = _ExplorerProvider.GetExplorerClient(cryptoCode);
|
||||||
var isImport = method == WalletSetupMethod.Seed;
|
var isImport = method == WalletSetupMethod.Seed;
|
||||||
var vm = new WalletSetupViewModel
|
var vm = new WalletSetupViewModel
|
||||||
|
@ -322,7 +330,7 @@ namespace BTCPayServer.Controllers
|
||||||
vm.RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString();
|
vm.RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString();
|
||||||
vm.AccountKey = response.AccountHDKey.Neuter().ToWif();
|
vm.AccountKey = response.AccountHDKey.Neuter().ToWif();
|
||||||
vm.KeyPath = response.AccountKeyPath.KeyPath.ToString();
|
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);
|
var result = await UpdateWallet(vm);
|
||||||
|
|
||||||
|
@ -398,12 +406,13 @@ namespace BTCPayServer.Controllers
|
||||||
(bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet();
|
(bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet();
|
||||||
var client = _ExplorerProvider.GetExplorerClient(network);
|
var client = _ExplorerProvider.GetExplorerClient(network);
|
||||||
|
|
||||||
|
var handler = _handlers.GetBitcoinHandler(cryptoCode);
|
||||||
var vm = new WalletSettingsViewModel
|
var vm = new WalletSettingsViewModel
|
||||||
{
|
{
|
||||||
StoreId = storeId,
|
StoreId = storeId,
|
||||||
CryptoCode = cryptoCode,
|
CryptoCode = cryptoCode,
|
||||||
WalletId = new WalletId(storeId, cryptoCode),
|
WalletId = new WalletId(storeId, cryptoCode),
|
||||||
Enabled = !excludeFilters.Match(derivation.PaymentId),
|
Enabled = !excludeFilters.Match(handler.PaymentMethodId),
|
||||||
Network = network,
|
Network = network,
|
||||||
IsHotWallet = derivation.IsHotWallet,
|
IsHotWallet = derivation.IsHotWallet,
|
||||||
Source = derivation.Source,
|
Source = derivation.Source,
|
||||||
|
@ -411,7 +420,7 @@ namespace BTCPayServer.Controllers
|
||||||
DerivationScheme = derivation.AccountDerivation.ToString(),
|
DerivationScheme = derivation.AccountDerivation.ToString(),
|
||||||
DerivationSchemeInput = derivation.AccountOriginal,
|
DerivationSchemeInput = derivation.AccountOriginal,
|
||||||
KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString(),
|
KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString(),
|
||||||
UriScheme = derivation.Network.NBitcoinNetwork.UriScheme,
|
UriScheme = network.NBitcoinNetwork.UriScheme,
|
||||||
Label = derivation.Label,
|
Label = derivation.Label,
|
||||||
SelectedSigningKey = derivation.SigningKey.ToString(),
|
SelectedSigningKey = derivation.SigningKey.ToString(),
|
||||||
NBXSeedAvailable = derivation.IsHotWallet &&
|
NBXSeedAvailable = derivation.IsHotWallet &&
|
||||||
|
@ -425,7 +434,7 @@ namespace BTCPayServer.Controllers
|
||||||
MasterFingerprint = e.RootFingerprint is HDFingerprint fp ? fp.ToString() : null,
|
MasterFingerprint = e.RootFingerprint is HDFingerprint fp ? fp.ToString() : null,
|
||||||
AccountKeyPath = e.AccountKeyPath == null ? "" : $"m/{e.AccountKeyPath}"
|
AccountKeyPath = e.AccountKeyPath == null ? "" : $"m/{e.AccountKeyPath}"
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
Config = ProtectString(derivation.ToJson()),
|
Config = ProtectString(JToken.FromObject(derivation, handler.Serializer).ToString()),
|
||||||
PayJoinEnabled = storeBlob.PayJoinEnabled,
|
PayJoinEnabled = storeBlob.PayJoinEnabled,
|
||||||
MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes,
|
MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes,
|
||||||
SpeedPolicy = store.SpeedPolicy,
|
SpeedPolicy = store.SpeedPolicy,
|
||||||
|
@ -433,10 +442,7 @@ namespace BTCPayServer.Controllers
|
||||||
RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget,
|
RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget,
|
||||||
CanUseHotWallet = canUseHotWallet,
|
CanUseHotWallet = canUseHotWallet,
|
||||||
CanUseRPCImport = rpcImport,
|
CanUseRPCImport = rpcImport,
|
||||||
CanUsePayJoin = canUseHotWallet && store
|
CanUsePayJoin = canUseHotWallet && network.SupportPayJoin && derivation.IsHotWallet,
|
||||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
|
||||||
.OfType<DerivationSchemeSettings>()
|
|
||||||
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet),
|
|
||||||
StoreName = store.StoreName,
|
StoreName = store.StoreName,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -451,7 +457,7 @@ namespace BTCPayServer.Controllers
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public async Task<IActionResult> UpdateWalletSettings(WalletSettingsViewModel vm)
|
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)
|
if (checkResult != null)
|
||||||
{
|
{
|
||||||
return checkResult;
|
return checkResult;
|
||||||
|
@ -462,17 +468,17 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
var handler = _handlers.GetBitcoinHandler(vm.CryptoCode);
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||||
var currentlyEnabled = !excludeFilters.Match(derivation.PaymentId);
|
var currentlyEnabled = !excludeFilters.Match(handler.PaymentMethodId);
|
||||||
bool enabledChanged = currentlyEnabled != vm.Enabled;
|
bool enabledChanged = currentlyEnabled != vm.Enabled;
|
||||||
bool needUpdate = enabledChanged;
|
bool needUpdate = enabledChanged;
|
||||||
string errorMessage = null;
|
string errorMessage = null;
|
||||||
|
|
||||||
if (enabledChanged)
|
if (enabledChanged)
|
||||||
{
|
{
|
||||||
storeBlob.SetExcluded(derivation.PaymentId, !vm.Enabled);
|
storeBlob.SetExcluded(handler.PaymentMethodId, !vm.Enabled);
|
||||||
store.SetStoreBlob(storeBlob);
|
store.SetStoreBlob(storeBlob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +490,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
var signingKey = string.IsNullOrEmpty(vm.SelectedSigningKey)
|
var signingKey = string.IsNullOrEmpty(vm.SelectedSigningKey)
|
||||||
? null
|
? null
|
||||||
: new BitcoinExtPubKey(vm.SelectedSigningKey, derivation.Network.NBitcoinNetwork);
|
: new BitcoinExtPubKey(vm.SelectedSigningKey, network.NBitcoinNetwork);
|
||||||
if (derivation.SigningKey != signingKey && signingKey != null)
|
if (derivation.SigningKey != signingKey && signingKey != null)
|
||||||
{
|
{
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
|
@ -531,9 +537,15 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (store.SpeedPolicy != vm.SpeedPolicy)
|
||||||
|
{
|
||||||
|
store.SpeedPolicy = vm.SpeedPolicy;
|
||||||
|
needUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (needUpdate)
|
if (needUpdate)
|
||||||
{
|
{
|
||||||
store.SetSupportedPaymentMethod(derivation);
|
store.SetPaymentMethodConfig(handler, derivation);
|
||||||
|
|
||||||
await _Repo.UpdateStore(store);
|
await _Repo.UpdateStore(store);
|
||||||
|
|
||||||
|
@ -561,7 +573,7 @@ namespace BTCPayServer.Controllers
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public async Task<IActionResult> UpdatePaymentSettings(WalletSettingsViewModel vm)
|
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)
|
if (checkResult != null)
|
||||||
{
|
{
|
||||||
return checkResult;
|
return checkResult;
|
||||||
|
@ -574,12 +586,6 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
bool needUpdate = false;
|
bool needUpdate = false;
|
||||||
if (store.SpeedPolicy != vm.SpeedPolicy)
|
|
||||||
{
|
|
||||||
needUpdate = true;
|
|
||||||
store.SpeedPolicy = vm.SpeedPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
var blob = store.GetStoreBlob();
|
var blob = store.GetStoreBlob();
|
||||||
var payjoinChanged = blob.PayJoinEnabled != vm.PayJoinEnabled;
|
var payjoinChanged = blob.PayJoinEnabled != vm.PayJoinEnabled;
|
||||||
blob.MonitoringExpiration = TimeSpan.FromMinutes(vm.MonitoringExpiration);
|
blob.MonitoringExpiration = TimeSpan.FromMinutes(vm.MonitoringExpiration);
|
||||||
|
@ -598,21 +604,17 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
|
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.Remove(WellKnownTempData.SuccessMessage);
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
{
|
{
|
||||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
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();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
store.SetPaymentMethodConfig(PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode), null);
|
||||||
store.SetSupportedPaymentMethod(paymentMethodId, null);
|
|
||||||
|
|
||||||
await _Repo.UpdateStore(store);
|
await _Repo.UpdateStore(store);
|
||||||
_EventAggregator.Publish(new WalletChangedEvent { WalletId = new WalletId(storeId, cryptoCode) });
|
_EventAggregator.Publish(new WalletChangedEvent { WalletId = new WalletId(storeId, cryptoCode) });
|
||||||
|
@ -755,7 +756,7 @@ namespace BTCPayServer.Controllers
|
||||||
return RedirectToAction(nameof(GeneralSettings), new { storeId });
|
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();
|
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||||
var deposit = new KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
var deposit = new KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||||
|
@ -769,7 +770,7 @@ namespace BTCPayServer.Controllers
|
||||||
var keyPath = deposit.GetKeyPath(i);
|
var keyPath = deposit.GetKeyPath(i);
|
||||||
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
|
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
|
||||||
var derivation = line.Derive(i);
|
var derivation = line.Derive(i);
|
||||||
var address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
|
var address = network.CreateAddress(strategy.AccountDerivation,
|
||||||
line.KeyPathTemplate.GetKeyPath(i),
|
line.KeyPathTemplate.GetKeyPath(i),
|
||||||
derivation.ScriptPubKey).ToString();
|
derivation.ScriptPubKey).ToString();
|
||||||
vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath));
|
vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath));
|
||||||
|
@ -792,11 +793,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
|
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
|
||||||
{
|
{
|
||||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
return store.GetPaymentMethodConfig<DerivationSchemeSettings>(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), _handlers);
|
||||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
|
||||||
.OfType<DerivationSchemeSettings>()
|
|
||||||
.FirstOrDefault(d => d.PaymentId == id);
|
|
||||||
return existing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetSeed(ExplorerClient client, DerivationSchemeSettings derivation)
|
private async Task<string> GetSeed(ExplorerClient client, DerivationSchemeSettings derivation)
|
||||||
|
|
|
@ -18,6 +18,7 @@ using BTCPayServer.HostedServices.Webhooks;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Security.Bitpay;
|
using BTCPayServer.Security.Bitpay;
|
||||||
|
@ -82,7 +83,7 @@ namespace BTCPayServer.Controllers
|
||||||
_LangService = langService;
|
_LangService = langService;
|
||||||
_TokenController = tokenController;
|
_TokenController = tokenController;
|
||||||
_WalletProvider = walletProvider;
|
_WalletProvider = walletProvider;
|
||||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
_handlers = paymentMethodHandlerDictionary;
|
||||||
_policiesSettings = policiesSettings;
|
_policiesSettings = policiesSettings;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
|
@ -118,7 +119,7 @@ namespace BTCPayServer.Controllers
|
||||||
readonly SettingsRepository _settingsRepository;
|
readonly SettingsRepository _settingsRepository;
|
||||||
private readonly ExplorerClientProvider _ExplorerProvider;
|
private readonly ExplorerClientProvider _ExplorerProvider;
|
||||||
private readonly LanguageService _LangService;
|
private readonly LanguageService _LangService;
|
||||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly PoliciesSettings _policiesSettings;
|
private readonly PoliciesSettings _policiesSettings;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
|
@ -337,16 +338,15 @@ namespace BTCPayServer.Controllers
|
||||||
var storeBlob = CurrentStore.GetStoreBlob();
|
var storeBlob = CurrentStore.GetStoreBlob();
|
||||||
var vm = new CheckoutAppearanceViewModel();
|
var vm = new CheckoutAppearanceViewModel();
|
||||||
SetCryptoCurrencies(vm, CurrentStore);
|
SetCryptoCurrencies(vm, CurrentStore);
|
||||||
vm.PaymentMethodCriteria = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider)
|
vm.PaymentMethodCriteria = CurrentStore.GetPaymentMethodConfigs(_handlers)
|
||||||
.Where(s => !storeBlob.GetExcludedPaymentMethods().Match(s.PaymentId))
|
.Where(s => !storeBlob.GetExcludedPaymentMethods().Match(s.Key) && s.Value is not LNURLPaymentMethodConfig)
|
||||||
.Where(s => _NetworkProvider.GetNetwork(s.PaymentId.CryptoCode) != null)
|
.Select(c =>
|
||||||
.Where(s => s.PaymentId.PaymentType != PaymentTypes.LNURLPay)
|
|
||||||
.Select(method =>
|
|
||||||
{
|
{
|
||||||
|
var pmi = c.Key;
|
||||||
var existing = storeBlob.PaymentMethodCriteria.SingleOrDefault(criteria =>
|
var existing = storeBlob.PaymentMethodCriteria.SingleOrDefault(criteria =>
|
||||||
criteria.PaymentMethod == method.PaymentId);
|
criteria.PaymentMethod == pmi);
|
||||||
return existing is null
|
return existing is null
|
||||||
? new PaymentMethodCriteriaViewModel { PaymentMethod = method.PaymentId.ToString(), Value = "" }
|
? new PaymentMethodCriteriaViewModel { PaymentMethod = pmi.ToString(), Value = "" }
|
||||||
: new PaymentMethodCriteriaViewModel
|
: new PaymentMethodCriteriaViewModel
|
||||||
{
|
{
|
||||||
PaymentMethod = existing.PaymentMethod.ToString(),
|
PaymentMethod = existing.PaymentMethod.ToString(),
|
||||||
|
@ -391,13 +391,13 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
public PaymentMethodOptionViewModel.Format[] GetEnabledPaymentMethodChoices(StoreData storeData)
|
public PaymentMethodOptionViewModel.Format[] GetEnabledPaymentMethodChoices(StoreData storeData)
|
||||||
{
|
{
|
||||||
var enabled = storeData.GetEnabledPaymentIds(_NetworkProvider);
|
var enabled = storeData.GetEnabledPaymentIds();
|
||||||
|
|
||||||
return enabled
|
return enabled
|
||||||
.Select(o =>
|
.Select(o =>
|
||||||
new PaymentMethodOptionViewModel.Format()
|
new PaymentMethodOptionViewModel.Format()
|
||||||
{
|
{
|
||||||
Name = o.ToPrettyString(),
|
Name = o.ToString(),
|
||||||
Value = o.ToString(),
|
Value = o.ToString(),
|
||||||
PaymentId = o
|
PaymentId = o
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
@ -405,13 +405,13 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
PaymentMethodOptionViewModel.Format? GetDefaultPaymentMethodChoice(StoreData storeData)
|
PaymentMethodOptionViewModel.Format? GetDefaultPaymentMethodChoice(StoreData storeData)
|
||||||
{
|
{
|
||||||
var enabled = storeData.GetEnabledPaymentIds(_NetworkProvider);
|
var enabled = storeData.GetEnabledPaymentIds();
|
||||||
var defaultPaymentId = storeData.GetDefaultPaymentId();
|
var defaultPaymentId = storeData.GetDefaultPaymentId();
|
||||||
var defaultChoice = defaultPaymentId is not null ? defaultPaymentId.FindNearest(enabled) : null;
|
var defaultChoice = defaultPaymentId is not null ? defaultPaymentId.FindNearest(enabled) : null;
|
||||||
if (defaultChoice is null)
|
if (defaultChoice is null)
|
||||||
{
|
{
|
||||||
defaultChoice = enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.BTCLike) ??
|
defaultChoice = enabled.FirstOrDefault(e => e == PaymentTypes.CHAIN.GetPaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode)) ??
|
||||||
enabled.FirstOrDefault(e => e.CryptoCode == _NetworkProvider.DefaultNetwork.CryptoCode && e.PaymentType == PaymentTypes.LightningLike) ??
|
enabled.FirstOrDefault(e => e == PaymentTypes.LN.GetPaymentMethodId(_NetworkProvider.DefaultNetwork.CryptoCode)) ??
|
||||||
enabled.FirstOrDefault();
|
enabled.FirstOrDefault();
|
||||||
}
|
}
|
||||||
var choices = GetEnabledPaymentMethodChoices(storeData);
|
var choices = GetEnabledPaymentMethodChoices(storeData);
|
||||||
|
@ -507,15 +507,15 @@ namespace BTCPayServer.Controllers
|
||||||
foreach (var newCriteria in model.PaymentMethodCriteria.ToList())
|
foreach (var newCriteria in model.PaymentMethodCriteria.ToList())
|
||||||
{
|
{
|
||||||
var paymentMethodId = PaymentMethodId.Parse(newCriteria.PaymentMethod);
|
var paymentMethodId = PaymentMethodId.Parse(newCriteria.PaymentMethod);
|
||||||
if (paymentMethodId.PaymentType == PaymentTypes.LightningLike)
|
if (_handlers.TryGet(paymentMethodId) is LightningLikePaymentHandler h)
|
||||||
model.PaymentMethodCriteria.Add(new PaymentMethodCriteriaViewModel()
|
model.PaymentMethodCriteria.Add(new PaymentMethodCriteriaViewModel()
|
||||||
{
|
{
|
||||||
PaymentMethod = new PaymentMethodId(paymentMethodId.CryptoCode, PaymentTypes.LNURLPay).ToString(),
|
PaymentMethod = PaymentTypes.LNURL.GetPaymentMethodId(h.Network.CryptoCode).ToString(),
|
||||||
Type = newCriteria.Type,
|
Type = newCriteria.Type,
|
||||||
Value = newCriteria.Value
|
Value = newCriteria.Value
|
||||||
});
|
});
|
||||||
// Should not be able to set LNUrlPay criteria directly in UI
|
// 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);
|
model.PaymentMethodCriteria.Remove(newCriteria);
|
||||||
}
|
}
|
||||||
blob.PaymentMethodCriteria ??= new List<PaymentMethodCriteria>();
|
blob.PaymentMethodCriteria ??= new List<PaymentMethodCriteria>();
|
||||||
|
@ -575,54 +575,49 @@ namespace BTCPayServer.Controllers
|
||||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||||
var derivationByCryptoCode =
|
var derivationByCryptoCode =
|
||||||
store
|
store
|
||||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
.GetPaymentMethodConfigs<DerivationSchemeSettings>(_handlers)
|
||||||
.OfType<DerivationSchemeSettings>()
|
.ToDictionary(c => ((IHasNetwork)_handlers[c.Key]).Network.CryptoCode, c => (DerivationSchemeSettings)c.Value);
|
||||||
.ToDictionary(c => c.Network.CryptoCode.ToUpperInvariant());
|
|
||||||
|
|
||||||
var lightningByCryptoCode = store
|
var lightningByCryptoCode = store
|
||||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
.GetPaymentMethodConfigs(_handlers)
|
||||||
.OfType<LightningSupportedPaymentMethod>()
|
.Where(c => c.Value is LightningPaymentMethodConfig)
|
||||||
.Where(method => method.PaymentId.PaymentType == LightningPaymentType.Instance)
|
.ToDictionary(c => ((IHasNetwork)_handlers[c.Key]).Network.CryptoCode, c => (LightningPaymentMethodConfig)c.Value);
|
||||||
.ToDictionary(c => c.CryptoCode.ToUpperInvariant());
|
|
||||||
|
|
||||||
derivationSchemes = new List<StoreDerivationScheme>();
|
derivationSchemes = new List<StoreDerivationScheme>();
|
||||||
lightningNodes = new List<StoreLightningNode>();
|
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(network.CryptoCode);
|
||||||
var strategy = derivationByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
var value = strategy?.ToPrettyString() ?? string.Empty;
|
||||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
|
||||||
var value = strategy?.ToPrettyString() ?? string.Empty;
|
|
||||||
|
|
||||||
derivationSchemes.Add(new StoreDerivationScheme
|
derivationSchemes.Add(new StoreDerivationScheme
|
||||||
{
|
{
|
||||||
Crypto = paymentMethodId.CryptoCode,
|
Crypto = network.CryptoCode,
|
||||||
WalletSupported = network.WalletSupported,
|
PaymentMethodId = handler.PaymentMethodId,
|
||||||
Value = value,
|
WalletSupported = network.WalletSupported,
|
||||||
WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode),
|
Value = value,
|
||||||
Enabled = !excludeFilters.Match(paymentMethodId) && strategy != null,
|
WalletId = new WalletId(store.Id, network.CryptoCode),
|
||||||
|
Enabled = !excludeFilters.Match(handler.PaymentMethodId) && strategy != null,
|
||||||
#if ALTCOINS
|
#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
|
#endif
|
||||||
});
|
});
|
||||||
break;
|
}
|
||||||
|
else if (handler is LightningLikePaymentHandler)
|
||||||
case LNURLPayPaymentType:
|
{
|
||||||
break;
|
var lnNetwork = ((IHasNetwork)handler).Network;
|
||||||
|
var lightning = lightningByCryptoCode.TryGet(lnNetwork.CryptoCode);
|
||||||
case LightningPaymentType _:
|
var isEnabled = !excludeFilters.Match(handler.PaymentMethodId) && lightning != null;
|
||||||
var lightning = lightningByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
lightningNodes.Add(new StoreLightningNode
|
||||||
var isEnabled = !excludeFilters.Match(paymentMethodId) && lightning != null;
|
{
|
||||||
lightningNodes.Add(new StoreLightningNode
|
CryptoCode = lnNetwork.CryptoCode,
|
||||||
{
|
PaymentMethodId = handler.PaymentMethodId,
|
||||||
CryptoCode = paymentMethodId.CryptoCode,
|
Address = lightning?.GetDisplayableConnectionString(),
|
||||||
Address = lightning?.GetDisplayableConnectionString(),
|
Enabled = isEnabled
|
||||||
Enabled = isEnabled
|
});
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -843,7 +838,7 @@ namespace BTCPayServer.Controllers
|
||||||
var isOD = Regex.Match(derivationScheme, @"\(.*?\)");
|
var isOD = Regex.Match(derivationScheme, @"\(.*?\)");
|
||||||
if (isOD.Success)
|
if (isOD.Success)
|
||||||
{
|
{
|
||||||
var derivationSchemeSettings = new DerivationSchemeSettings { Network = network };
|
var derivationSchemeSettings = new DerivationSchemeSettings();
|
||||||
var result = parser.ParseOutputDescriptor(derivationScheme);
|
var result = parser.ParseOutputDescriptor(derivationScheme);
|
||||||
derivationSchemeSettings.AccountOriginal = derivationScheme.Trim();
|
derivationSchemeSettings.AccountOriginal = derivationScheme.Trim();
|
||||||
derivationSchemeSettings.AccountDerivation = result.Item1;
|
derivationSchemeSettings.AccountDerivation = result.Item1;
|
||||||
|
@ -1087,8 +1082,8 @@ namespace BTCPayServer.Controllers
|
||||||
if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
|
if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
|
||||||
{
|
{
|
||||||
var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods();
|
var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods();
|
||||||
StoreNotConfigured = !store.GetSupportedPaymentMethods(_NetworkProvider)
|
StoreNotConfigured = !store.GetPaymentMethodConfigs(_handlers)
|
||||||
.Where(p => !excludeFilter.Match(p.PaymentId))
|
.Where(p => !excludeFilter.Match(p.Key))
|
||||||
.Any();
|
.Any();
|
||||||
TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful";
|
TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful";
|
||||||
if (pairingResult == PairingResult.Partial)
|
if (pairingResult == PairingResult.Partial)
|
||||||
|
|
|
@ -9,6 +9,9 @@ using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Hwi;
|
using BTCPayServer.Hwi;
|
||||||
using BTCPayServer.ModelBinders;
|
using BTCPayServer.ModelBinders;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -22,15 +25,15 @@ namespace BTCPayServer.Controllers
|
||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
|
||||||
public class UIVaultController : Controller
|
public class UIVaultController : Controller
|
||||||
{
|
{
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
|
||||||
public UIVaultController(BTCPayNetworkProvider networks, IAuthorizationService authorizationService)
|
public UIVaultController(PaymentMethodHandlerDictionary handlers, IAuthorizationService authorizationService)
|
||||||
{
|
{
|
||||||
Networks = networks;
|
_handlers = handlers;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BTCPayNetworkProvider Networks { get; }
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{cryptoCode}/xpub")]
|
[Route("{cryptoCode}/xpub")]
|
||||||
|
@ -46,8 +49,7 @@ namespace BTCPayServer.Controllers
|
||||||
using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
|
using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
|
||||||
{
|
{
|
||||||
var cancellationToken = cts.Token;
|
var cancellationToken = cts.Token;
|
||||||
var network = Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
|
if (!_handlers.TryGetValue(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), out var h) || h is not IHasNetwork { Network: var network })
|
||||||
if (network == null)
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||||
var vaultClient = new VaultClient(websocket);
|
var vaultClient = new VaultClient(websocket);
|
||||||
|
@ -397,11 +399,8 @@ askdevice:
|
||||||
|
|
||||||
private DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId)
|
private DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId)
|
||||||
{
|
{
|
||||||
var paymentMethod = CurrentStore
|
var pmi = Payments.PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||||
.GetSupportedPaymentMethods(Networks)
|
return CurrentStore.GetPaymentMethodConfig<DerivationSchemeSettings>(pmi, _handlers);
|
||||||
.OfType<DerivationSchemeSettings>()
|
|
||||||
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode);
|
|
||||||
return paymentMethod;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ namespace BTCPayServer.Controllers
|
||||||
// we just assume that it is 20 blocks
|
// we just assume that it is 20 blocks
|
||||||
var assumedFeeRate = await fr.GetFeeRateAsync(20);
|
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)
|
if (derivationScheme is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ using BTCPayServer.ModelBinders;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.WalletViewModels;
|
using BTCPayServer.Models.WalletViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Payments.PayJoin;
|
using BTCPayServer.Payments.PayJoin;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Labels;
|
using BTCPayServer.Services.Labels;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
|
@ -69,6 +71,8 @@ namespace BTCPayServer.Controllers
|
||||||
private readonly DelayedTransactionBroadcaster _broadcaster;
|
private readonly DelayedTransactionBroadcaster _broadcaster;
|
||||||
private readonly PayjoinClient _payjoinClient;
|
private readonly PayjoinClient _payjoinClient;
|
||||||
private readonly LabelService _labelService;
|
private readonly LabelService _labelService;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
|
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
||||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||||
private readonly WalletHistogramService _walletHistogramService;
|
private readonly WalletHistogramService _walletHistogramService;
|
||||||
|
@ -94,10 +98,14 @@ namespace BTCPayServer.Controllers
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
PullPaymentHostedService pullPaymentHostedService,
|
PullPaymentHostedService pullPaymentHostedService,
|
||||||
LabelService labelService,
|
LabelService labelService,
|
||||||
|
PaymentMethodHandlerDictionary handlers,
|
||||||
|
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||||
TransactionLinkProviders transactionLinkProviders)
|
TransactionLinkProviders transactionLinkProviders)
|
||||||
{
|
{
|
||||||
_currencyTable = currencyTable;
|
_currencyTable = currencyTable;
|
||||||
_labelService = labelService;
|
_labelService = labelService;
|
||||||
|
_handlers = handlers;
|
||||||
|
_paymentModelExtensions = paymentModelExtensions;
|
||||||
_transactionLinkProviders = transactionLinkProviders;
|
_transactionLinkProviders = transactionLinkProviders;
|
||||||
Repository = repo;
|
Repository = repo;
|
||||||
WalletRepository = walletRepository;
|
WalletRepository = walletRepository;
|
||||||
|
@ -176,11 +184,11 @@ namespace BTCPayServer.Controllers
|
||||||
var stores = await Repository.GetStoresByUserId(GetUserId());
|
var stores = await Repository.GetStoresByUserId(GetUserId());
|
||||||
|
|
||||||
var onChainWallets = stores
|
var onChainWallets = stores
|
||||||
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
|
.SelectMany(s => s.GetPaymentMethodConfigs<DerivationSchemeSettings>(_handlers)
|
||||||
.OfType<DerivationSchemeSettings>()
|
.Select(d => (
|
||||||
.Select(d => ((Wallet: _walletProvider.GetWallet(d.Network),
|
Wallet: _walletProvider.GetWallet(((IHasNetwork)_handlers[d.Key]).Network),
|
||||||
DerivationStrategy: d.AccountDerivation,
|
DerivationStrategy: d.Value.AccountDerivation,
|
||||||
Network: d.Network)))
|
Network: ((IHasNetwork)_handlers[d.Key]).Network))
|
||||||
.Where(_ => _.Wallet != null && _.Network.WalletSupported)
|
.Where(_ => _.Wallet != null && _.Network.WalletSupported)
|
||||||
.Select(_ => (Wallet: _.Wallet,
|
.Select(_ => (Wallet: _.Wallet,
|
||||||
Store: s,
|
Store: s,
|
||||||
|
@ -225,8 +233,8 @@ namespace BTCPayServer.Controllers
|
||||||
var paymentMethod = GetDerivationSchemeSettings(walletId);
|
var paymentMethod = GetDerivationSchemeSettings(walletId);
|
||||||
if (paymentMethod == null)
|
if (paymentMethod == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
var network = _handlers.GetBitcoinHandler(walletId.CryptoCode).Network;
|
||||||
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
|
var wallet = _walletProvider.GetWallet(network);
|
||||||
|
|
||||||
// We can't filter at the database level if we need to apply label filter
|
// We can't filter at the database level if we need to apply label filter
|
||||||
var preFiltering = string.IsNullOrEmpty(labelFilter);
|
var preFiltering = string.IsNullOrEmpty(labelFilter);
|
||||||
|
@ -253,12 +261,12 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var pmi = new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike);
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||||
foreach (var tx in transactions)
|
foreach (var tx in transactions)
|
||||||
{
|
{
|
||||||
var vm = new ListTransactionsViewModel.TransactionViewModel();
|
var vm = new ListTransactionsViewModel.TransactionViewModel();
|
||||||
vm.Id = tx.TransactionId.ToString();
|
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.Timestamp = tx.SeenAt;
|
||||||
vm.Positive = tx.BalanceChange.GetValue(wallet.Network) >= 0;
|
vm.Positive = tx.BalanceChange.GetValue(wallet.Network) >= 0;
|
||||||
vm.Balance = tx.BalanceChange.ShowMoney(wallet.Network);
|
vm.Balance = tx.BalanceChange.ShowMoney(wallet.Network);
|
||||||
|
@ -325,7 +333,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey,
|
bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey,
|
||||||
Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint",
|
Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint",
|
||||||
new { walletId.CryptoCode })));
|
new { cryptoCode = walletId.CryptoCode })));
|
||||||
}
|
}
|
||||||
|
|
||||||
string[]? labels = null;
|
string[]? labels = null;
|
||||||
|
@ -340,7 +348,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
CryptoCode = walletId.CryptoCode,
|
CryptoCode = walletId.CryptoCode,
|
||||||
Address = address?.ToString(),
|
Address = address?.ToString(),
|
||||||
CryptoImage = GetImage(paymentMethod.PaymentId, network),
|
CryptoImage = GetImage(network),
|
||||||
PaymentLink = bip21.ToString(),
|
PaymentLink = bip21.ToString(),
|
||||||
ReturnUrl = returnUrl ?? HttpContext.Request.GetTypedHeaders().Referer?.AbsolutePath,
|
ReturnUrl = returnUrl ?? HttpContext.Request.GetTypedHeaders().Referer?.AbsolutePath,
|
||||||
SelectedLabels = labels ?? Array.Empty<string>()
|
SelectedLabels = labels ?? Array.Empty<string>()
|
||||||
|
@ -449,7 +457,7 @@ namespace BTCPayServer.Controllers
|
||||||
var storeData = store.GetStoreBlob();
|
var storeData = store.GetStoreBlob();
|
||||||
var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider);
|
var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider);
|
||||||
rateRules.Spread = 0.0m;
|
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);
|
double.TryParse(defaultAmount, out var amount);
|
||||||
|
|
||||||
var model = new WalletSendModel
|
var model = new WalletSendModel
|
||||||
|
@ -591,7 +599,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
var utxos = await _walletProvider.GetWallet(network)
|
var utxos = await _walletProvider.GetWallet(network)
|
||||||
.GetUnspentCoins(schemeSettings.AccountDerivation, false, cancellation);
|
.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,
|
var walletTransactionsInfoAsync = await this.WalletRepository.GetWalletTransactionsInfo(walletId,
|
||||||
utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
|
utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
|
||||||
vm.InputsAvailable = utxos.Select(coin =>
|
vm.InputsAvailable = utxos.Select(coin =>
|
||||||
|
@ -606,7 +614,7 @@ namespace BTCPayServer.Controllers
|
||||||
Amount = coin.Value.GetValue(network),
|
Amount = coin.Value.GetValue(network),
|
||||||
Comment = info?.Comment,
|
Comment = info?.Comment,
|
||||||
Labels = _labelService.CreateTransactionTagModels(info, Request),
|
Labels = _labelService.CreateTransactionTagModels(info, Request),
|
||||||
Link = _transactionLinkProviders.GetTransactionLink(pmi, coin.OutPoint.ToString()),
|
Link = _transactionLinkProviders.GetTransactionLink(network.CryptoCode, coin.OutPoint.ToString()),
|
||||||
Confirmations = coin.Confirmations
|
Confirmations = coin.Confirmations
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
@ -753,7 +761,7 @@ namespace BTCPayServer.Controllers
|
||||||
CreatePSBTResponse psbtResponse;
|
CreatePSBTResponse psbtResponse;
|
||||||
if (command == "schedule")
|
if (command == "schedule")
|
||||||
{
|
{
|
||||||
var pmi = new PaymentMethodId(walletId.CryptoCode, BitcoinPaymentType.Instance);
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode);
|
||||||
var claims =
|
var claims =
|
||||||
vm.Outputs.Where(output => string.IsNullOrEmpty(output.PayoutId)).Select(output => new ClaimRequest()
|
vm.Outputs.Where(output => string.IsNullOrEmpty(output.PayoutId)).Select(output => new ClaimRequest()
|
||||||
{
|
{
|
||||||
|
@ -1211,7 +1219,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
internal DerivationSchemeSettings? GetDerivationSchemeSettings(WalletId walletId)
|
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,
|
private static async Task<IMoney> GetBalanceAsMoney(BTCPayWallet wallet,
|
||||||
|
@ -1251,7 +1259,8 @@ namespace BTCPayServer.Controllers
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||||
if (derivationScheme == null || derivationScheme.Network.ReadonlyWallet)
|
var network = _handlers.GetBitcoinHandler(walletId.CryptoCode).Network;
|
||||||
|
if (derivationScheme == null || network.ReadonlyWallet)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
switch (command)
|
switch (command)
|
||||||
|
@ -1335,7 +1344,8 @@ namespace BTCPayServer.Controllers
|
||||||
if (paymentMethod == null)
|
if (paymentMethod == null)
|
||||||
return NotFound();
|
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 walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId, (string[]?)null);
|
||||||
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, cancellationToken: cancellationToken);
|
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, cancellationToken: cancellationToken);
|
||||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||||
|
@ -1469,12 +1479,14 @@ namespace BTCPayServer.Controllers
|
||||||
return RedirectToAction(nameof(WalletLabels), new { walletId });
|
return RedirectToAction(nameof(WalletLabels), new { walletId });
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
|
private string? GetImage(BTCPayNetwork network)
|
||||||
{
|
{
|
||||||
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||||
? Url.Content(network.CryptoImagePath)
|
if (_paymentModelExtensions.TryGetValue(pmi, out var extension))
|
||||||
: Url.Content(network.LightningImagePath);
|
{
|
||||||
return Request.GetRelativePathOrAbsolute(res);
|
return Request.GetRelativePathOrAbsolute(Url.Content(extension.Image));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetUserId() => _userManager.GetUserId(User)!;
|
private string GetUserId() => _userManager.GetUserId(User)!;
|
||||||
|
|
|
@ -15,11 +15,6 @@ namespace BTCPayServer.Data
|
||||||
return addressInvoiceData.Address;
|
return addressInvoiceData.Address;
|
||||||
return addressInvoiceData.Address.Substring(0, index);
|
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)
|
public static PaymentMethodId GetPaymentMethodId(this AddressInvoiceData addressInvoiceData)
|
||||||
{
|
{
|
||||||
if (addressInvoiceData.Address == null)
|
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
|
public static class IHasBlobExtensions
|
||||||
{
|
{
|
||||||
static readonly JsonSerializerSettings DefaultSerializer;
|
static readonly JsonSerializerSettings DefaultSerializerSettings;
|
||||||
|
static readonly JsonSerializer DefaultSerializer;
|
||||||
static IHasBlobExtensions()
|
static IHasBlobExtensions()
|
||||||
{
|
{
|
||||||
DefaultSerializer = new JsonSerializerSettings()
|
DefaultSerializerSettings = new JsonSerializerSettings()
|
||||||
{
|
{
|
||||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
|
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
|
||||||
Formatting = Formatting.None
|
Formatting = Formatting.None
|
||||||
};
|
};
|
||||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializer);
|
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
||||||
|
DefaultSerializer = JsonSerializer.CreateDefault(DefaultSerializerSettings);
|
||||||
}
|
}
|
||||||
class HasBlobWrapper<B> : IHasBlob<B>
|
class HasBlobWrapper<B> : IHasBlob<B>
|
||||||
{
|
{
|
||||||
|
@ -60,7 +62,7 @@ namespace BTCPayServer.Data
|
||||||
public static B? GetBlob<B>(this IHasBlob<B> data, JsonSerializerSettings? settings = null)
|
public static B? GetBlob<B>(this IHasBlob<B> data, JsonSerializerSettings? settings = null)
|
||||||
{
|
{
|
||||||
if (data.Blob2 is not 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
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
if (data.Blob is not null && data.Blob.Length != 0)
|
if (data.Blob is not null && data.Blob.Length != 0)
|
||||||
{
|
{
|
||||||
|
@ -69,7 +71,7 @@ namespace BTCPayServer.Data
|
||||||
str = Encoding.UTF8.GetString(data.Blob);
|
str = Encoding.UTF8.GetString(data.Blob);
|
||||||
else
|
else
|
||||||
str = ZipUtils.Unzip(data.Blob);
|
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
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
return default;
|
return default;
|
||||||
|
@ -78,19 +80,28 @@ namespace BTCPayServer.Data
|
||||||
public static object? GetBlob(this IHasBlob data, JsonSerializerSettings? settings = null)
|
public static object? GetBlob(this IHasBlob data, JsonSerializerSettings? settings = null)
|
||||||
{
|
{
|
||||||
if (data.Blob2 is not 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
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
if (data.Blob is not null && data.Blob.Length != 0)
|
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
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
return default;
|
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)
|
if (blob is null)
|
||||||
data.Blob2 = null;
|
data.Blob2 = null;
|
||||||
else
|
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
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
data.Blob = new byte[0];
|
data.Blob = new byte[0];
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
|
@ -1,48 +1,69 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection.Metadata;
|
using System.Reflection.Metadata;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public static class InvoiceDataExtensions
|
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)
|
public static void SetBlob(this InvoiceData invoiceData, InvoiceEntity blob)
|
||||||
{
|
{
|
||||||
if (blob.Metadata is null)
|
if (blob.Metadata is null)
|
||||||
blob.Metadata = new InvoiceMetadata();
|
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
|
#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.Price = price;
|
||||||
entity.Networks = networks;
|
|
||||||
if (entity.Metadata is null)
|
|
||||||
{
|
|
||||||
if (entity.Version < InvoiceEntity.GreenfieldInvoices_Version)
|
|
||||||
{
|
|
||||||
entity.MigrateLegacyInvoice();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
entity.Metadata = new InvoiceMetadata();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entity;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
entity.StoreId = invoiceData.StoreDataId;
|
||||||
|
entity.ExceptionStatus = state.ExceptionStatus;
|
||||||
|
entity.Status = state.Status;
|
||||||
|
if (invoiceData.AddressInvoices != null)
|
||||||
{
|
{
|
||||||
var entity = invoiceData.HasTypedBlob<InvoiceEntity>().GetBlob();
|
entity.AvailableAddressHashes = invoiceData.AddressInvoices.Select(a => a.GetAddress() + a.GetPaymentMethodId()).ToHashSet();
|
||||||
entity.Networks = networks;
|
|
||||||
return entity;
|
|
||||||
}
|
}
|
||||||
#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)
|
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 System.Runtime.InteropServices;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
public static class PaymentDataExtensions
|
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();
|
var prompt = invoiceEntity.GetPaymentPrompt(handler.PaymentMethodId) ?? throw new InvalidOperationException($"Payment prompt for {handler.PaymentMethodId} is not found");
|
||||||
paymentData.Blob2 = entity.Network.ToString(entity);
|
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
|
paymentData.Amount = entity.Value;
|
||||||
if (paymentData.Blob is not null && paymentData.Blob.Length != 0)
|
paymentData.Currency = entity.Currency;
|
||||||
{
|
paymentData.Status = entity.Status;
|
||||||
var unziped = ZipUtils.Unzip(paymentData.Blob);
|
paymentData.SetBlob(entity.PaymentMethodId, (PaymentBlob)entity);
|
||||||
var cryptoCode = "BTC";
|
return entity;
|
||||||
if (JObject.Parse(unziped).TryGetValue("cryptoCode", out var v) && v.Type == JTokenType.String)
|
}
|
||||||
cryptoCode = v.Value<string>();
|
public static PaymentData SetBlob(this PaymentData paymentData, PaymentMethodId paymentMethodId, PaymentBlob blob)
|
||||||
var network = networks.GetNetwork<BTCPayNetworkBase>(cryptoCode);
|
{
|
||||||
PaymentEntity paymentEntity = null;
|
paymentData.Type = paymentMethodId.ToString();
|
||||||
if (network == null)
|
paymentData.Blob2 = JToken.FromObject(blob, InvoiceDataExtensions.DefaultSerializer).ToString(Newtonsoft.Json.Formatting.None);
|
||||||
{
|
return paymentData;
|
||||||
return null;
|
}
|
||||||
}
|
|
||||||
else
|
public static PaymentEntity GetBlob(this PaymentData paymentData)
|
||||||
{
|
{
|
||||||
paymentEntity = network.ToObject<PaymentEntity>(unziped);
|
var entity = JToken.Parse(paymentData.Blob2).ToObject<PaymentEntity>(InvoiceDataExtensions.DefaultSerializer) ?? throw new FormatException($"Invalid {nameof(PaymentEntity)}");
|
||||||
}
|
entity.Status = paymentData.Status!.Value;
|
||||||
paymentEntity.Network = network;
|
entity.Currency = paymentData.Currency;
|
||||||
paymentEntity.Accounted = paymentData.Accounted;
|
entity.PaymentMethodId = PaymentMethodId.Parse(paymentData.Type);
|
||||||
return paymentEntity;
|
entity.Value = paymentData.Amount!.Value;
|
||||||
}
|
entity.Id = paymentData.Id;
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
entity.ReceivedTime = paymentData.Created!.Value;
|
||||||
if (paymentData.Blob2 is not null)
|
return entity;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,9 @@ using BTCPayServer.Events;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Notifications;
|
using BTCPayServer.Services.Notifications;
|
||||||
using BTCPayServer.Services.Notifications.Blobs;
|
using BTCPayServer.Services.Notifications.Blobs;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -32,6 +34,7 @@ using StoreData = BTCPayServer.Data.StoreData;
|
||||||
public class BitcoinLikePayoutHandler : IPayoutHandler
|
public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
{
|
{
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
|
@ -43,6 +46,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
public WalletRepository WalletRepository { get; }
|
public WalletRepository WalletRepository { get; }
|
||||||
|
|
||||||
public BitcoinLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
|
public BitcoinLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
|
PaymentMethodHandlerDictionary handlers,
|
||||||
WalletRepository walletRepository,
|
WalletRepository walletRepository,
|
||||||
ExplorerClientProvider explorerClientProvider,
|
ExplorerClientProvider explorerClientProvider,
|
||||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
||||||
|
@ -53,6 +57,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
TransactionLinkProviders transactionLinkProviders)
|
TransactionLinkProviders transactionLinkProviders)
|
||||||
{
|
{
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||||
|
_handlers = handlers;
|
||||||
WalletRepository = walletRepository;
|
WalletRepository = walletRepository;
|
||||||
_explorerClientProvider = explorerClientProvider;
|
_explorerClientProvider = explorerClientProvider;
|
||||||
_jsonSerializerSettings = jsonSerializerSettings;
|
_jsonSerializerSettings = jsonSerializerSettings;
|
||||||
|
@ -66,20 +71,20 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
|
|
||||||
public bool CanHandle(PaymentMethodId paymentMethod)
|
public bool CanHandle(PaymentMethodId paymentMethod)
|
||||||
{
|
{
|
||||||
return paymentMethod?.PaymentType == BitcoinPaymentType.Instance &&
|
return _handlers.TryGetValue(paymentMethod, out var h) &&
|
||||||
_btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.CryptoCode)?.ReadonlyWallet is false;
|
h is BitcoinLikePaymentHandler { Network: { ReadonlyWallet: false } };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData)
|
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);
|
var explorerClient = _explorerClientProvider.GetExplorerClient(network);
|
||||||
if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
|
||||||
{
|
{
|
||||||
|
|
||||||
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
|
await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
|
||||||
await WalletRepository.AddWalletTransactionAttachment(
|
await WalletRepository.AddWalletTransactionAttachment(
|
||||||
new WalletId(claimRequest.StoreId, claimRequest.PaymentMethodId.CryptoCode),
|
new WalletId(claimRequest.StoreId, network.CryptoCode),
|
||||||
bitcoinLikeClaimDestination.Address.ToString(),
|
bitcoinLikeClaimDestination.Address.ToString(),
|
||||||
Attachment.Payout(payoutData.PullPaymentDataId, payoutData.Id), WalletObjectData.Types.Address);
|
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)
|
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();
|
destination = destination.Trim();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -116,19 +121,19 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
return null;
|
return null;
|
||||||
var paymentMethodId = payout.GetPaymentMethodId();
|
var paymentMethodId = payout.GetPaymentMethodId();
|
||||||
if (paymentMethodId is null)
|
if (paymentMethodId is null)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
var cryptoCode = _handlers.TryGetNetwork(paymentMethodId)?.CryptoCode;
|
||||||
|
if (cryptoCode is null)
|
||||||
|
return null;
|
||||||
ParseProofType(payout.Proof, out var raw, out var proofType);
|
ParseProofType(payout.Proof, out var raw, out var proofType);
|
||||||
if (proofType == PayoutTransactionOnChainBlob.Type)
|
if (proofType == PayoutTransactionOnChainBlob.Type)
|
||||||
{
|
{
|
||||||
|
|
||||||
var res = raw.ToObject<PayoutTransactionOnChainBlob>(
|
var res = raw.ToObject<PayoutTransactionOnChainBlob>(
|
||||||
JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(paymentMethodId.CryptoCode)));
|
JsonSerializer.Create(_jsonSerializerSettings.GetSerializer(cryptoCode)));
|
||||||
if (res == null)
|
if (res == null)
|
||||||
return null;
|
return null;
|
||||||
res.LinkTemplate = _transactionLinkProviders.GetBlockExplorerLink(paymentMethodId);
|
res.LinkTemplate = _transactionLinkProviders.GetBlockExplorerLink(cryptoCode);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
return raw.ToObject<ManualPayoutProof>();
|
return raw.ToObject<ManualPayoutProof>();
|
||||||
|
@ -181,7 +186,8 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
|
|
||||||
public Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
|
public Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
|
||||||
{
|
{
|
||||||
if (_btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode)?
|
var network = _handlers.TryGetNetwork(paymentMethodId);
|
||||||
|
if (network?
|
||||||
.NBitcoinNetwork?
|
.NBitcoinNetwork?
|
||||||
.Consensus?
|
.Consensus?
|
||||||
.ConsensusFactory?
|
.ConsensusFactory?
|
||||||
|
@ -269,25 +275,23 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
|
|
||||||
public Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
|
public Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
|
||||||
{
|
{
|
||||||
return Task.FromResult(storeData.GetEnabledPaymentIds(_btcPayNetworkProvider)
|
return Task.FromResult(storeData.GetPaymentMethodConfigs<DerivationSchemeSettings>(_handlers, true).Select(c => c.Key));
|
||||||
.Where(id => id.PaymentType == BitcoinPaymentType.Instance));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)
|
public async Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)
|
||||||
{
|
{
|
||||||
await using var ctx = this._dbContextFactory.CreateContext();
|
await using var ctx = this._dbContextFactory.CreateContext();
|
||||||
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||||
var pmi = paymentMethodId.ToString();
|
var pmi = paymentMethodId;
|
||||||
|
|
||||||
var payouts = await ctx.Payouts.Include(data => data.PullPaymentData)
|
var payouts = await ctx.Payouts.Include(data => data.PullPaymentData)
|
||||||
.Where(data => payoutIds.Contains(data.Id)
|
.Where(data => payoutIds.Contains(data.Id)
|
||||||
&& pmi == data.PaymentMethodId
|
&& pmi.ToString() == data.PaymentMethodId
|
||||||
&& data.State == PayoutState.AwaitingPayment)
|
&& data.State == PayoutState.AwaitingPayment)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var pullPaymentIds = payouts.Select(data => data.PullPaymentDataId).Distinct().Where(s => s != null).ToArray();
|
var pullPaymentIds = payouts.Select(data => data.PullPaymentDataId).Distinct().Where(s => s != null).ToArray();
|
||||||
var storeId = payouts.First().StoreDataId;
|
var storeId = payouts.First().StoreDataId;
|
||||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
var network = _handlers.GetNetwork(paymentMethodId);
|
||||||
List<string> bip21 = new List<string>();
|
List<string> bip21 = new List<string>();
|
||||||
foreach (var payout in payouts)
|
foreach (var payout in payouts)
|
||||||
{
|
{
|
||||||
|
@ -315,10 +319,10 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bip21.Any())
|
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
|
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
|
pullPaymentId = pullPaymentIds.Length == 1 ? pullPaymentIds.First() : null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -418,7 +422,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
var destinationSum =
|
var destinationSum =
|
||||||
newTransaction.NewTransactionEvent.Outputs.Sum(output => output.Value.GetValue(network));
|
newTransaction.NewTransactionEvent.Outputs.Sum(output => output.Value.GetValue(network));
|
||||||
var destination = addressTrackedSource.Address.ToString();
|
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();
|
await using var ctx = _dbContextFactory.CreateContext();
|
||||||
var payouts = await ctx.Payouts
|
var payouts = await ctx.Payouts
|
||||||
|
@ -443,7 +447,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var derivationSchemeSettings = payout.StoreData
|
var derivationSchemeSettings = payout.StoreData
|
||||||
.GetDerivationSchemeSettings(_btcPayNetworkProvider, newTransaction.CryptoCode)?.AccountDerivation;
|
.GetDerivationSchemeSettings(_handlers, newTransaction.CryptoCode)?.AccountDerivation;
|
||||||
if (derivationSchemeSettings is null)
|
if (derivationSchemeSettings is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,10 @@ using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using LNURL;
|
using LNURL;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -25,16 +27,15 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
|
|
||||||
public const string LightningLikePayoutHandlerClearnetNamedClient =
|
public const string LightningLikePayoutHandlerClearnetNamedClient =
|
||||||
nameof(LightningLikePayoutHandlerClearnetNamedClient);
|
nameof(LightningLikePayoutHandlerClearnetNamedClient);
|
||||||
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly UserService _userService;
|
private readonly UserService _userService;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
|
||||||
public LightningLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
|
public LightningLikePayoutHandler(PaymentMethodHandlerDictionary handlers,
|
||||||
IHttpClientFactory httpClientFactory, UserService userService, IAuthorizationService authorizationService)
|
IHttpClientFactory httpClientFactory, UserService userService, IAuthorizationService authorizationService)
|
||||||
{
|
{
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
_handlers = handlers;
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
|
@ -42,8 +43,8 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
|
|
||||||
public bool CanHandle(PaymentMethodId paymentMethod)
|
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)
|
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)
|
public async Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
destination = destination.Trim();
|
destination = destination.Trim();
|
||||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
var network = ((IHasNetwork)_handlers[paymentMethodId]).Network;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string lnurlTag = null;
|
string lnurlTag = null;
|
||||||
|
@ -161,12 +162,12 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
public async Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
|
public async Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
|
||||||
{
|
{
|
||||||
var result = new List<PaymentMethodId>();
|
var result = new List<PaymentMethodId>();
|
||||||
var methods = storeData.GetEnabledPaymentMethods(_btcPayNetworkProvider).Where(id => id.PaymentId.PaymentType == LightningPaymentType.Instance).OfType<LightningSupportedPaymentMethod>();
|
var methods = storeData.GetPaymentMethodConfigs<LightningPaymentMethodConfig>(_handlers, true);
|
||||||
foreach (LightningSupportedPaymentMethod supportedPaymentMethod in methods)
|
foreach (var m in methods)
|
||||||
{
|
{
|
||||||
if (!supportedPaymentMethod.IsInternalNode)
|
if (!m.Value.IsInternalNode)
|
||||||
{
|
{
|
||||||
result.Add(supportedPaymentMethod.PaymentId);
|
result.Add(m.Key);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +175,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
{
|
{
|
||||||
if (!await _userService.IsAdminUser(storeDataUserStore.ApplicationUserId))
|
if (!await _userService.IsAdminUser(storeDataUserStore.ApplicationUserId))
|
||||||
continue;
|
continue;
|
||||||
result.Add(supportedPaymentMethod.PaymentId);
|
result.Add(m.Key);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,8 +186,9 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
|
|
||||||
public Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)
|
public Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)
|
||||||
{
|
{
|
||||||
|
var cryptoCode = _handlers.GetNetwork(paymentMethodId).CryptoCode;
|
||||||
return Task.FromResult<IActionResult>(new RedirectToActionResult("ConfirmLightningPayout",
|
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.HostedServices;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using LNURL;
|
using LNURL;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -32,7 +34,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
|
||||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly LightningClientFactoryService _lightningClientFactoryService;
|
private readonly LightningClientFactoryService _lightningClientFactoryService;
|
||||||
private readonly IOptions<LightningNetworkOptions> _options;
|
private readonly IOptions<LightningNetworkOptions> _options;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
@ -43,7 +45,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
|
||||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
PaymentMethodHandlerDictionary handlers,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
LightningClientFactoryService lightningClientFactoryService,
|
LightningClientFactoryService lightningClientFactoryService,
|
||||||
IOptions<LightningNetworkOptions> options,
|
IOptions<LightningNetworkOptions> options,
|
||||||
|
@ -54,7 +56,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
|
||||||
_payoutHandlers = payoutHandlers;
|
_payoutHandlers = payoutHandlers;
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
_handlers = handlers;
|
||||||
_lightningClientFactoryService = lightningClientFactoryService;
|
_lightningClientFactoryService = lightningClientFactoryService;
|
||||||
_options = options;
|
_options = options;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
|
@ -101,7 +103,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
{
|
{
|
||||||
await SetStoreContext();
|
await SetStoreContext();
|
||||||
|
|
||||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||||
|
|
||||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||||
var payouts = await GetPayouts(ctx, pmi, payoutIds);
|
var payouts = await GetPayouts(ctx, pmi, payoutIds);
|
||||||
|
@ -125,14 +127,14 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
||||||
{
|
{
|
||||||
await SetStoreContext();
|
await SetStoreContext();
|
||||||
|
|
||||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
var pmi = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
|
||||||
var payoutHandler = (LightningLikePayoutHandler)_payoutHandlers.FindPayoutHandler(pmi);
|
var payoutHandler = (LightningLikePayoutHandler)_payoutHandlers.FindPayoutHandler(pmi);
|
||||||
|
|
||||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||||
|
|
||||||
var payouts = (await GetPayouts(ctx, pmi, payoutIds)).GroupBy(data => data.StoreDataId);
|
var payouts = (await GetPayouts(ctx, pmi, payoutIds)).GroupBy(data => data.StoreDataId);
|
||||||
var results = new List<ResultVM>();
|
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
|
//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 store = payoutDatas.First().StoreData;
|
||||||
|
|
||||||
var lightningSupportedPaymentMethod = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
var lightningSupportedPaymentMethod = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _handlers);
|
||||||
.OfType<LightningSupportedPaymentMethod>()
|
|
||||||
.FirstOrDefault(method => method.PaymentId == pmi);
|
|
||||||
|
|
||||||
if (lightningSupportedPaymentMethod.IsInternalNode && !authorizedForInternalNode)
|
if (lightningSupportedPaymentMethod.IsInternalNode && !authorizedForInternalNode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,12 +5,15 @@ using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using static Org.BouncyCastle.Math.EC.ECCurve;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
|
@ -24,21 +27,19 @@ namespace BTCPayServer.Data
|
||||||
return defaultPaymentId;
|
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();
|
return storeData.GetPaymentMethodConfigs(true)
|
||||||
var paymentMethodIds = storeData.GetSupportedPaymentMethods(networks)
|
.Where(m => handlers.Support(m.Key))
|
||||||
.Where(a => !excludeFilter.Match(a.PaymentId))
|
.OrderByDescending(a => a.Key.ToString() == "BTC")
|
||||||
.OrderByDescending(a => a.PaymentId.CryptoCode == "BTC")
|
.ThenBy(a => a.Key.ToString())
|
||||||
.ThenBy(a => a.PaymentId.CryptoCode)
|
.ThenBy(a => handlers[a.Key].ParsePaymentMethodConfig(a.Value) is LightningPaymentMethodConfig ? 1 : 0)
|
||||||
.ThenBy(a => a.PaymentId.PaymentType == PaymentTypes.LightningLike ? 1 : 0)
|
.ToDictionary(a => a.Key, a => handlers[a.Key].ParsePaymentMethodConfig(a.Value));
|
||||||
.ToArray();
|
|
||||||
return paymentMethodIds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetDefaultPaymentId(this StoreData storeData, PaymentMethodId? defaultPaymentId)
|
public static void SetDefaultPaymentId(this StoreData storeData, PaymentMethodId? defaultPaymentId)
|
||||||
|
@ -60,12 +61,9 @@ namespace BTCPayServer.Data
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool AnyPaymentMethodAvailable(this StoreData storeData, BTCPayNetworkProvider networkProvider)
|
public static bool AnyPaymentMethodAvailable(this StoreData storeData)
|
||||||
{
|
{
|
||||||
var storeBlob = GetStoreBlob(storeData);
|
return storeData.GetPaymentMethodConfigs(true).Any();
|
||||||
var excludeFilter = storeBlob.GetExcludedPaymentMethods();
|
|
||||||
|
|
||||||
return GetSupportedPaymentMethods(storeData, networkProvider).Where(s => !excludeFilter.Match(s.PaymentId)).Any();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool SetStoreBlob(this StoreData storeData, StoreBlob storeBlob)
|
public static bool SetStoreBlob(this StoreData storeData, StoreBlob storeBlob)
|
||||||
|
@ -78,106 +76,94 @@ namespace BTCPayServer.Data
|
||||||
return true;
|
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);
|
var config = GetPaymentMethodConfig(storeData, paymentMethodId, onlyEnabled);
|
||||||
#pragma warning disable CS0618
|
if (config is null || !handlers.Support(paymentMethodId))
|
||||||
bool btcReturned = false;
|
return null;
|
||||||
|
return handlers[paymentMethodId].ParsePaymentMethodConfig(config);
|
||||||
if (!string.IsNullOrEmpty(storeData.DerivationStrategies))
|
}
|
||||||
|
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);
|
JObject strategies = JObject.Parse(storeData.DerivationStrategies);
|
||||||
foreach (var strat in strategies.Properties())
|
return strategies[paymentMethodId.ToString()];
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#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));
|
||||||
}
|
}
|
||||||
|
public static void SetPaymentMethodConfig(this StoreData storeData, PaymentMethodId paymentMethodId, JToken? config)
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
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);
|
JObject strategies = string.IsNullOrEmpty(storeData.DerivationStrategies) ? new JObject() : JObject.Parse(storeData.DerivationStrategies);
|
||||||
bool existing = false;
|
if (config is null)
|
||||||
foreach (var strat in strategies.Properties().ToList())
|
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;
|
continue;
|
||||||
}
|
if (excludeFilter?.Match(paymentMethodId) is true)
|
||||||
if (stratId == paymentMethodId)
|
continue;
|
||||||
{
|
paymentMethodConfigurations.Add(paymentMethodId, strat.Value);
|
||||||
if (supportedPaymentMethod == null)
|
|
||||||
{
|
|
||||||
strat.Remove();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
strat.Value = PaymentMethodExtensions.Serialize(supportedPaymentMethod);
|
|
||||||
}
|
|
||||||
existing = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!existing && supportedPaymentMethod != null)
|
return paymentMethodConfigurations;
|
||||||
strategies.Add(new JProperty(supportedPaymentMethod.PaymentId.ToString(), PaymentMethodExtensions.Serialize(supportedPaymentMethod)));
|
|
||||||
storeData.DerivationStrategies = strategies.ToString();
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
return storeData.GetPaymentMethodConfig(paymentMethodId, true) is not null;
|
||||||
var excludeFilters = storeData.GetStoreBlob().GetExcludedPaymentMethods();
|
|
||||||
return paymentMethods.Any(method =>
|
|
||||||
method.PaymentId.CryptoCode == cryptoCode &&
|
|
||||||
method.PaymentId.PaymentType == paymentType &&
|
|
||||||
!excludeFilters.Match(method.PaymentId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
{
|
{
|
||||||
public class DerivationSchemeSettings : ISupportedPaymentMethod
|
public class DerivationSchemeSettings
|
||||||
{
|
{
|
||||||
public static DerivationSchemeSettings Parse(string derivationStrategy, BTCPayNetwork network)
|
public static DerivationSchemeSettings Parse(string derivationStrategy, BTCPayNetwork network)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(network);
|
ArgumentNullException.ThrowIfNull(network);
|
||||||
ArgumentNullException.ThrowIfNull(derivationStrategy);
|
ArgumentNullException.ThrowIfNull(derivationStrategy);
|
||||||
var result = new DerivationSchemeSettings { Network = network };
|
var result = new DerivationSchemeSettings();
|
||||||
var parser = network.GetDerivationSchemeParser();
|
var parser = network.GetDerivationSchemeParser();
|
||||||
if (parser.TryParseXpub(derivationStrategy, ref result) ||
|
if (parser.TryParseXpub(derivationStrategy, ref result) ||
|
||||||
parser.TryParseXpub(derivationStrategy, ref result, electrum: true))
|
parser.TryParseXpub(derivationStrategy, ref result, electrum: true))
|
||||||
|
@ -26,23 +26,9 @@ namespace BTCPayServer
|
||||||
throw new FormatException($"Invalid Derivation Scheme");
|
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);
|
return AccountDerivation is null ? null : DBUtils.nbxv1_get_wallet_id(network.NetworkSet.CryptoCode, AccountDerivation.ToString());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DerivationSchemeSettings()
|
public DerivationSchemeSettings()
|
||||||
|
@ -55,7 +41,6 @@ namespace BTCPayServer
|
||||||
ArgumentNullException.ThrowIfNull(network);
|
ArgumentNullException.ThrowIfNull(network);
|
||||||
ArgumentNullException.ThrowIfNull(derivationStrategy);
|
ArgumentNullException.ThrowIfNull(derivationStrategy);
|
||||||
AccountDerivation = derivationStrategy;
|
AccountDerivation = derivationStrategy;
|
||||||
Network = network;
|
|
||||||
AccountKeySettings = derivationStrategy.GetExtPubKeys().Select(c => new AccountKeySettings()
|
AccountKeySettings = derivationStrategy.GetExtPubKeys().Select(c => new AccountKeySettings()
|
||||||
{
|
{
|
||||||
AccountKey = c.GetWif(network.NBitcoinNetwork)
|
AccountKey = c.GetWif(network.NBitcoinNetwork)
|
||||||
|
@ -75,9 +60,6 @@ namespace BTCPayServer
|
||||||
_SigningKey = value;
|
_SigningKey = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public BTCPayNetwork Network { get; set; }
|
|
||||||
public string Source { get; set; }
|
public string Source { get; set; }
|
||||||
|
|
||||||
public bool IsHotWallet { get; set; }
|
public bool IsHotWallet { get; set; }
|
||||||
|
@ -97,49 +79,15 @@ namespace BTCPayServer
|
||||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
public BitcoinExtPubKey ExplicitAccountKey { get; set; }
|
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()
|
public AccountKeySettings GetSigningAccountKeySettings()
|
||||||
{
|
{
|
||||||
return AccountKeySettings.Single(a => a.AccountKey == SigningKey);
|
return AccountKeySettings.Single(a => a.AccountKey == SigningKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountKeySettings[] _AccountKeySettings;
|
|
||||||
public AccountKeySettings[] AccountKeySettings
|
public AccountKeySettings[] AccountKeySettings
|
||||||
{
|
{
|
||||||
get
|
get;
|
||||||
{
|
set;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<NBXplorer.Models.PSBTRebaseKeyRules> GetPSBTRebaseKeyRules()
|
public IEnumerable<NBXplorer.Models.PSBTRebaseKeyRules> GetPSBTRebaseKeyRules()
|
||||||
|
@ -159,9 +107,6 @@ namespace BTCPayServer
|
||||||
|
|
||||||
public string Label { get; set; }
|
public string Label { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public PaymentMethodId PaymentId => new PaymentMethodId(Network.CryptoCode, PaymentTypes.BTCLike);
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return AccountDerivation.ToString();
|
return AccountDerivation.ToString();
|
||||||
|
@ -173,11 +118,6 @@ namespace BTCPayServer
|
||||||
ToString();
|
ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToJson()
|
|
||||||
{
|
|
||||||
return Network.NBXplorerNetwork.Serializer.ToString(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RebaseKeyPaths(PSBT psbt)
|
public void RebaseKeyPaths(PSBT psbt)
|
||||||
{
|
{
|
||||||
foreach (var rebase in GetPSBTRebaseKeyRules())
|
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