mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 22:44:29 +01:00
Remove JSON in strings from JObjects (#4703)
This commit is contained in:
@@ -1218,21 +1218,14 @@ namespace BTCPayServer.Tests
|
|||||||
{(null, new Dictionary<string, object>())},
|
{(null, new Dictionary<string, object>())},
|
||||||
{("", new Dictionary<string, object>())},
|
{("", new Dictionary<string, object>())},
|
||||||
{("{}", new Dictionary<string, object>())},
|
{("{}", new Dictionary<string, object>())},
|
||||||
{("non-json-content", new Dictionary<string, object>() {{string.Empty, "non-json-content"}})},
|
|
||||||
{("[1,2,3]", new Dictionary<string, object>() {{string.Empty, "[1,2,3]"}})},
|
|
||||||
{("{ \"key\": \"value\"}", new Dictionary<string, object>() {{"key", "value"}})},
|
{("{ \"key\": \"value\"}", new Dictionary<string, object>() {{"key", "value"}})},
|
||||||
{("{ \"key\": true}", new Dictionary<string, object>() {{"key", "True"}})},
|
|
||||||
{
|
|
||||||
("{ invalidjson file here}",
|
|
||||||
new Dictionary<string, object>() {{String.Empty, "{ invalidjson file here}"}})
|
|
||||||
},
|
|
||||||
// Duplicate keys should not crash things
|
// Duplicate keys should not crash things
|
||||||
{("{ \"key\": true, \"key\": true}", new Dictionary<string, object>() {{"key", "True"}})}
|
{("{ \"key\": true, \"key\": true}", new Dictionary<string, object>() {{"key", "True"}})}
|
||||||
};
|
};
|
||||||
|
|
||||||
testCases.ForEach(tuple =>
|
testCases.ForEach(tuple =>
|
||||||
{
|
{
|
||||||
Assert.Equal(tuple.expectedOutput, UIInvoiceController.PosDataParser.ParsePosData(tuple.input));
|
Assert.Equal(tuple.expectedOutput, UIInvoiceController.PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -1806,6 +1799,70 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParseMetadata()
|
||||||
|
{
|
||||||
|
var metadata = InvoiceMetadata.FromJObject(JObject.Parse("{\"posData\": {\"test\":\"a\"}}"));
|
||||||
|
Assert.Equal(JObject.Parse("{\"test\":\"a\"}").ToString(), metadata.PosDataLegacy);
|
||||||
|
Assert.Equal(JObject.Parse("{\"test\":\"a\"}").ToString(), metadata.PosData.ToString());
|
||||||
|
|
||||||
|
// Legacy, as string
|
||||||
|
metadata = InvoiceMetadata.FromJObject(JObject.Parse("{\"posData\": \"{\\\"test\\\":\\\"a\\\"}\"}"));
|
||||||
|
Assert.Equal("{\"test\":\"a\"}", metadata.PosDataLegacy);
|
||||||
|
Assert.Equal(JObject.Parse("{\"test\":\"a\"}").ToString(), metadata.PosData.ToString());
|
||||||
|
|
||||||
|
metadata = InvoiceMetadata.FromJObject(JObject.Parse("{\"posData\": \"nobject\"}"));
|
||||||
|
Assert.Equal("nobject", metadata.PosDataLegacy);
|
||||||
|
Assert.Null(metadata.PosData);
|
||||||
|
|
||||||
|
metadata = InvoiceMetadata.FromJObject(JObject.Parse("{\"posData\": null}"));
|
||||||
|
Assert.Null(metadata.PosDataLegacy);
|
||||||
|
Assert.Null(metadata.PosData);
|
||||||
|
|
||||||
|
metadata = InvoiceMetadata.FromJObject(JObject.Parse("{}"));
|
||||||
|
Assert.Null(metadata.PosDataLegacy);
|
||||||
|
Assert.Null(metadata.PosData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParseInvoiceEntityDerivationStrategies()
|
||||||
|
{
|
||||||
|
// We have 3 ways of serializing the derivation strategies:
|
||||||
|
// through "derivationStrategy", through "derivationStrategies" as a string, through "derivationStrategies" as JObject
|
||||||
|
// Let's check that InvoiceEntity is similar in all cases.
|
||||||
|
var legacy = new JObject()
|
||||||
|
{
|
||||||
|
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf"
|
||||||
|
};
|
||||||
|
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", new BTCPayNetworkProvider(ChainName.Regtest).BTC);
|
||||||
|
|
||||||
|
scheme.Source = "ManualDerivationScheme";
|
||||||
|
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf";
|
||||||
|
var legacy2 = new JObject()
|
||||||
|
{
|
||||||
|
["derivationStrategies"] = scheme.ToJson()
|
||||||
|
};
|
||||||
|
|
||||||
|
var newformat = new JObject()
|
||||||
|
{
|
||||||
|
["derivationStrategies"] = JObject.Parse(scheme.ToJson())
|
||||||
|
};
|
||||||
|
|
||||||
|
//new BTCPayNetworkProvider(ChainName.Regtest)
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
var formats = new[] { legacy, legacy2, newformat }
|
||||||
|
.Select(o =>
|
||||||
|
{
|
||||||
|
var entity = JsonConvert.DeserializeObject<InvoiceEntity>(o.ToString());
|
||||||
|
entity.Networks = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||||
|
return entity.DerivationStrategies.ToString();
|
||||||
|
})
|
||||||
|
.ToHashSet();
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
Assert.Equal(1, formats.Count);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PaymentMethodIdConverterIsGraceful()
|
public void PaymentMethodIdConverterIsGraceful()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1695,37 +1695,17 @@ namespace BTCPayServer.Tests
|
|||||||
var testCases =
|
var testCases =
|
||||||
new List<(string input, Dictionary<string, object> expectedOutput)>()
|
new List<(string input, Dictionary<string, object> expectedOutput)>()
|
||||||
{
|
{
|
||||||
{(null, new Dictionary<string, object>())},
|
|
||||||
{("", new Dictionary<string, object>())},
|
|
||||||
{("{}", new Dictionary<string, object>())},
|
|
||||||
{
|
|
||||||
("non-json-content",
|
|
||||||
new Dictionary<string, object>() {{string.Empty, "non-json-content"}})
|
|
||||||
},
|
|
||||||
{("[1,2,3]", new Dictionary<string, object>() {{string.Empty, "[1,2,3]"}})},
|
|
||||||
{("{ \"key\": \"value\"}", new Dictionary<string, object>() {{"key", "value"}})},
|
{("{ \"key\": \"value\"}", new Dictionary<string, object>() {{"key", "value"}})},
|
||||||
{("{ \"key\": true}", new Dictionary<string, object>() {{"key", "True"}})},
|
{("{ \"key\": true}", new Dictionary<string, object>() {{"key", "True"}})}
|
||||||
{
|
|
||||||
("{ invalidjson file here}",
|
|
||||||
new Dictionary<string, object>() {{String.Empty, "{ invalidjson file here}"}})
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
|
||||||
foreach (var valueTuple in testCases)
|
foreach (var valueTuple in testCases)
|
||||||
{
|
{
|
||||||
tasks.Add(user.BitPay.CreateInvoiceAsync(new Invoice(1, "BTC") { PosData = valueTuple.input })
|
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(1, "BTC") { PosData = valueTuple.input });
|
||||||
.ContinueWith(async task =>
|
var result = await controller.Invoice(invoice.Id);
|
||||||
{
|
var viewModel = result.AssertViewModel<InvoiceDetailsModel>();
|
||||||
var result = await controller.Invoice(task.Result.Id);
|
Assert.Equal(valueTuple.expectedOutput, viewModel.AdditionalData["posData"]);
|
||||||
var viewModel =
|
|
||||||
Assert.IsType<InvoiceDetailsModel>(
|
|
||||||
Assert.IsType<ViewResult>(result).Model);
|
|
||||||
Assert.Equal(valueTuple.expectedOutput, viewModel.PosData);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Timeout = LongRunningTestTimeout)]
|
[Fact(Timeout = LongRunningTestTimeout)]
|
||||||
|
|||||||
@@ -40,6 +40,17 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
public partial class UIInvoiceController
|
public partial class UIInvoiceController
|
||||||
{
|
{
|
||||||
|
static UIInvoiceController()
|
||||||
|
{
|
||||||
|
InvoiceAdditionalDataExclude =
|
||||||
|
typeof(InvoiceMetadata)
|
||||||
|
.GetProperties()
|
||||||
|
.Select(p => p.Name)
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
InvoiceAdditionalDataExclude.Remove(nameof(InvoiceMetadata.PosData));
|
||||||
|
}
|
||||||
|
static readonly HashSet<string> InvoiceAdditionalDataExclude;
|
||||||
|
|
||||||
[HttpGet("invoices/{invoiceId}/deliveries/{deliveryId}/request")]
|
[HttpGet("invoices/{invoiceId}/deliveries/{deliveryId}/request")]
|
||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public async Task<IActionResult> WebhookDelivery(string invoiceId, string deliveryId)
|
public async Task<IActionResult> WebhookDelivery(string invoiceId, string deliveryId)
|
||||||
@@ -106,13 +117,9 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
var receipt = InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, invoice.ReceiptOptions);
|
var receipt = InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, invoice.ReceiptOptions);
|
||||||
var invoiceState = invoice.GetInvoiceState();
|
var invoiceState = invoice.GetInvoiceState();
|
||||||
var posData = PosDataParser.ParsePosData(invoice.Metadata.PosData);
|
var metaData = PosDataParser.ParsePosData(invoice.Metadata.ToJObject());
|
||||||
var metaData = PosDataParser.ParsePosData(invoice.Metadata.ToJObject().ToString());
|
|
||||||
var excludes = typeof(InvoiceMetadata).GetProperties()
|
|
||||||
.Select(p => char.ToLowerInvariant(p.Name[0]) + p.Name[1..])
|
|
||||||
.ToList();
|
|
||||||
var additionalData = metaData
|
var additionalData = metaData
|
||||||
.Where(dict => !excludes.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
|
||||||
{
|
{
|
||||||
@@ -139,7 +146,6 @@ namespace BTCPayServer.Controllers
|
|||||||
TypedMetadata = invoice.Metadata,
|
TypedMetadata = invoice.Metadata,
|
||||||
StatusException = invoice.ExceptionStatus,
|
StatusException = invoice.ExceptionStatus,
|
||||||
Events = invoice.Events,
|
Events = invoice.Events,
|
||||||
PosData = posData,
|
|
||||||
Metadata = metaData,
|
Metadata = metaData,
|
||||||
AdditionalData = additionalData,
|
AdditionalData = additionalData,
|
||||||
Archived = invoice.Archived,
|
Archived = invoice.Archived,
|
||||||
@@ -236,9 +242,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
vm.Amount = payments.Sum(p => p!.Paid);
|
vm.Amount = payments.Sum(p => p!.Paid);
|
||||||
vm.Payments = receipt.ShowPayments is false ? null : payments;
|
vm.Payments = receipt.ShowPayments is false ? null : payments;
|
||||||
vm.AdditionalData = receiptData is null
|
vm.AdditionalData = PosDataParser.ParsePosData(receiptData);
|
||||||
? new Dictionary<string, object>()
|
|
||||||
: PosDataParser.ParsePosData(receiptData.ToString());
|
|
||||||
|
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
@@ -1239,30 +1243,20 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
public class PosDataParser
|
public class PosDataParser
|
||||||
{
|
{
|
||||||
public static Dictionary<string, object> ParsePosData(string posData)
|
public static Dictionary<string, object> ParsePosData(JToken? posData)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, object>();
|
var result = new Dictionary<string, object>();
|
||||||
if (string.IsNullOrEmpty(posData))
|
if (posData is JObject jobj)
|
||||||
{
|
{
|
||||||
return result;
|
foreach (var item in jobj)
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var jObject = JObject.Parse(posData);
|
|
||||||
foreach (var item in jObject)
|
|
||||||
{
|
{
|
||||||
ParsePosDataItem(item, ref result);
|
ParsePosDataItem(item, ref result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
result.TryAdd(string.Empty, posData);
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ParsePosDataItem(KeyValuePair<string, JToken?> item, ref Dictionary<string, object> result)
|
static void ParsePosDataItem(KeyValuePair<string, JToken?> item, ref Dictionary<string, object> result)
|
||||||
{
|
{
|
||||||
switch (item.Value?.Type)
|
switch (item.Value?.Type)
|
||||||
{
|
{
|
||||||
@@ -1270,12 +1264,12 @@ namespace BTCPayServer.Controllers
|
|||||||
var items = item.Value.AsEnumerable().ToList();
|
var items = item.Value.AsEnumerable().ToList();
|
||||||
for (var i = 0; i < items.Count; i++)
|
for (var i = 0; i < items.Count; i++)
|
||||||
{
|
{
|
||||||
result.TryAdd($"{item.Key}[{i}]", ParsePosData(items[i].ToString()));
|
result.TryAdd($"{item.Key}[{i}]", ParsePosData(items[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case JTokenType.Object:
|
case JTokenType.Object:
|
||||||
result.TryAdd(item.Key, ParsePosData(item.Value.ToString()));
|
result.TryAdd(item.Key, ParsePosData(item.Value));
|
||||||
break;
|
break;
|
||||||
case null:
|
case null:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
||||||
using StoreData = BTCPayServer.Data.StoreData;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
|
|
||||||
@@ -117,7 +118,7 @@ namespace BTCPayServer.Controllers
|
|||||||
throw new BitpayHttpException(400, "The expirationTime is set too soon");
|
throw new BitpayHttpException(400, "The expirationTime is set too soon");
|
||||||
}
|
}
|
||||||
entity.Metadata.OrderId = invoice.OrderId;
|
entity.Metadata.OrderId = invoice.OrderId;
|
||||||
entity.Metadata.PosData = invoice.PosData;
|
entity.Metadata.PosDataLegacy = invoice.PosData;
|
||||||
entity.ServerUrl = serverUrl;
|
entity.ServerUrl = serverUrl;
|
||||||
entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications;
|
entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications;
|
||||||
entity.ExtendedNotifications = invoice.ExtendedNotifications;
|
entity.ExtendedNotifications = invoice.ExtendedNotifications;
|
||||||
|
|||||||
71
BTCPayServer/IHasAdditionalData.cs
Normal file
71
BTCPayServer/IHasAdditionalData.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NBitcoin;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace BTCPayServer
|
||||||
|
{
|
||||||
|
public static class HasAdditionalDataExtensions
|
||||||
|
{
|
||||||
|
public static T GetAdditionalData<T>(this IHasAdditionalData o, string propName)
|
||||||
|
{
|
||||||
|
if (o.AdditionalData == null || !(o.AdditionalData.TryGetValue(propName, out var jt) is true))
|
||||||
|
return default;
|
||||||
|
if (jt.Type == JTokenType.Null)
|
||||||
|
return default;
|
||||||
|
if (typeof(T) == typeof(string))
|
||||||
|
{
|
||||||
|
return (T)(object)jt.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return jt.Value<T>();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void SetAdditionalData<T>(this IHasAdditionalData o, string propName, T value)
|
||||||
|
{
|
||||||
|
JToken data;
|
||||||
|
if (typeof(T) == typeof(string) && value is string v)
|
||||||
|
{
|
||||||
|
data = new JValue(v);
|
||||||
|
o.AdditionalData ??= new Dictionary<string, JToken>();
|
||||||
|
o.AdditionalData.AddOrReplace(propName, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
o.AdditionalData?.Remove(propName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (value is string s)
|
||||||
|
{
|
||||||
|
data = JToken.Parse(s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = JToken.FromObject(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
data = JToken.FromObject(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
o.AdditionalData ??= new Dictionary<string, JToken>();
|
||||||
|
o.AdditionalData.AddOrReplace(propName, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public interface IHasAdditionalData
|
||||||
|
{
|
||||||
|
IDictionary<string, JToken> AdditionalData { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -124,7 +124,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
public DateTimeOffset MonitoringDate { get; internal set; }
|
public DateTimeOffset MonitoringDate { get; internal set; }
|
||||||
public List<Data.InvoiceEventData> Events { get; internal set; }
|
public List<Data.InvoiceEventData> Events { get; internal set; }
|
||||||
public string NotificationEmail { get; internal set; }
|
public string NotificationEmail { get; internal set; }
|
||||||
public Dictionary<string, object> PosData { get; set; }
|
|
||||||
public Dictionary<string, object> Metadata { get; set; }
|
public Dictionary<string, object> Metadata { get; set; }
|
||||||
public Dictionary<string, object> AdditionalData { get; set; }
|
public Dictionary<string, object> AdditionalData { get; set; }
|
||||||
public List<PaymentEntity> Payments { get; set; }
|
public List<PaymentEntity> Payments { get; set; }
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||||||
{
|
{
|
||||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType });
|
||||||
}
|
}
|
||||||
|
var jposData = TryParseJObject(posData);
|
||||||
string title;
|
string title;
|
||||||
decimal? price;
|
decimal? price;
|
||||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||||
@@ -191,10 +192,9 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||||||
|
|
||||||
price = amount;
|
price = amount;
|
||||||
title = settings.Title;
|
title = settings.Title;
|
||||||
|
|
||||||
//if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items
|
//if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items
|
||||||
if (!string.IsNullOrEmpty(posData) && currentView == PosViewType.Cart &&
|
if (currentView == PosViewType.Cart &&
|
||||||
AppService.TryParsePosCartItems(posData, out var cartItems))
|
AppService.TryParsePosCartItems(jposData, out var cartItems))
|
||||||
{
|
{
|
||||||
var choices = _appService.GetPOSItems(settings.Template, settings.Currency);
|
var choices = _appService.GetPOSItems(settings.Template, settings.Currency);
|
||||||
var expectedMinimumAmount = 0m;
|
var expectedMinimumAmount = 0m;
|
||||||
@@ -257,7 +257,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||||||
return View("PostRedirect", vm);
|
return View("PostRedirect", vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
formResponseJObject = JObject.Parse(formResponse);
|
formResponseJObject = TryParseJObject(formResponse) ?? new JObject();
|
||||||
var form = Form.Parse(formData.Config);
|
var form = Form.Parse(formData.Config);
|
||||||
form.SetValues(formResponseJObject);
|
form.SetValues(formResponseJObject);
|
||||||
if (!FormDataService.Validate(form, ModelState))
|
if (!FormDataService.Validate(form, ModelState))
|
||||||
@@ -269,7 +269,6 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||||||
formResponseJObject = form.GetValues();
|
formResponseJObject = form.GetValues();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest
|
var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest
|
||||||
@@ -287,7 +286,6 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||||||
: Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), "UIPointOfSale", new { appId, viewType })),
|
: Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), "UIPointOfSale", new { appId, viewType })),
|
||||||
FullNotifications = true,
|
FullNotifications = true,
|
||||||
ExtendedNotifications = true,
|
ExtendedNotifications = true,
|
||||||
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
|
||||||
RedirectAutomatically = settings.RedirectAutomatically,
|
RedirectAutomatically = settings.RedirectAutomatically,
|
||||||
SupportedTransactionCurrencies = paymentMethods,
|
SupportedTransactionCurrencies = paymentMethods,
|
||||||
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
|
||||||
@@ -298,23 +296,11 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||||||
cancellationToken, entity =>
|
cancellationToken, entity =>
|
||||||
{
|
{
|
||||||
entity.Metadata.OrderUrl = Request.GetDisplayUrl();
|
entity.Metadata.OrderUrl = Request.GetDisplayUrl();
|
||||||
|
entity.Metadata.PosData = jposData;
|
||||||
if (formResponseJObject is null) return;
|
if (formResponseJObject is null) return;
|
||||||
var meta = entity.Metadata.ToJObject();
|
var meta = entity.Metadata.ToJObject();
|
||||||
if (formResponseJObject.ContainsKey("posData") && meta.TryGetValue("posData", out var posDataValue) && posDataValue.Type == JTokenType.String)
|
meta.Merge(formResponseJObject);
|
||||||
{
|
entity.Metadata = InvoiceMetadata.FromJObject(meta);
|
||||||
try
|
|
||||||
{
|
|
||||||
meta["posData"] = JObject.Parse(posDataValue.Value<string>());
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// ignored as we don't want to break the invoice creation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
formResponseJObject.Merge(meta);
|
|
||||||
entity.Metadata = InvoiceMetadata.FromJObject(formResponseJObject);
|
|
||||||
});
|
});
|
||||||
return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", new { invoiceId = invoice.Data.Id });
|
return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", new { invoiceId = invoice.Data.Id });
|
||||||
}
|
}
|
||||||
@@ -330,6 +316,18 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JObject TryParseJObject(string posData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JObject.Parse(posData);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("/apps/{appId}/pos/form/{viewType?}")]
|
[HttpPost("/apps/{appId}/pos/form/{viewType?}")]
|
||||||
public async Task<IActionResult> POSForm(string appId, PosViewType? viewType = null)
|
public async Task<IActionResult> POSForm(string appId, PosViewType? viewType = null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -18,6 +19,7 @@ using Ganss.XSS;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.DataEncoders;
|
using NBitcoin.DataEncoders;
|
||||||
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
@@ -250,7 +252,7 @@ namespace BTCPayServer.Services.Apps
|
|||||||
var itemCount = paidInvoices
|
var itemCount = paidInvoices
|
||||||
.Where(entity => entity.Currency.Equals(settings.Currency, StringComparison.OrdinalIgnoreCase) && (
|
.Where(entity => entity.Currency.Equals(settings.Currency, StringComparison.OrdinalIgnoreCase) && (
|
||||||
// The POS data is present for the cart view, where multiple items can be bought
|
// The POS data is present for the cart view, where multiple items can be bought
|
||||||
!string.IsNullOrEmpty(entity.Metadata.PosData) ||
|
entity.Metadata.PosData != null ||
|
||||||
// The item code should be present for all types other than the cart and keypad
|
// The item code should be present for all types other than the cart and keypad
|
||||||
!string.IsNullOrEmpty(entity.Metadata.ItemCode)
|
!string.IsNullOrEmpty(entity.Metadata.ItemCode)
|
||||||
))
|
))
|
||||||
@@ -335,10 +337,10 @@ namespace BTCPayServer.Services.Apps
|
|||||||
{
|
{
|
||||||
return (res, e) =>
|
return (res, e) =>
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(e.Metadata.PosData))
|
if (e.Metadata.PosData != null)
|
||||||
{
|
{
|
||||||
// flatten single items from POS data
|
// flatten single items from POS data
|
||||||
var data = JsonConvert.DeserializeObject<PosAppData>(e.Metadata.PosData);
|
var data = e.Metadata.PosData.ToObject<PosAppData>();
|
||||||
if (data is not { Cart.Length: > 0 })
|
if (data is not { Cart.Length: > 0 })
|
||||||
return res;
|
return res;
|
||||||
foreach (var lineItem in data.Cart)
|
foreach (var lineItem in data.Cart)
|
||||||
@@ -777,18 +779,33 @@ namespace BTCPayServer.Services.Apps
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable enable
|
||||||
public static bool TryParsePosCartItems(string posData, out Dictionary<string, int> cartItems)
|
public static bool TryParsePosCartItems(JObject? posData, [MaybeNullWhen(false)] out Dictionary<string, int> cartItems)
|
||||||
{
|
{
|
||||||
cartItems = null;
|
cartItems = null;
|
||||||
if (!TryParseJson(posData, out var posDataObj) ||
|
if (posData is null)
|
||||||
!posDataObj.TryGetValue("cart", out var cartObject))
|
|
||||||
return false;
|
return false;
|
||||||
cartItems = cartObject.Select(token => (JObject)token)
|
if (!posData.TryGetValue("cart", out var cartObject))
|
||||||
.ToDictionary(o => o.GetValue("id", StringComparison.InvariantCulture)?.ToString(),
|
return false;
|
||||||
o => int.Parse(o.GetValue("count", StringComparison.InvariantCulture)?.ToString() ?? string.Empty, CultureInfo.InvariantCulture));
|
if (cartObject is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cartItems = new();
|
||||||
|
foreach (var o in cartObject.OfType<JObject>())
|
||||||
|
{
|
||||||
|
var id = o.GetValue("id", StringComparison.InvariantCulture)?.ToString();
|
||||||
|
if (id != null)
|
||||||
|
{
|
||||||
|
var countStr = o.GetValue("count", StringComparison.InvariantCulture)?.ToString() ?? string.Empty;
|
||||||
|
if (int.TryParse(countStr, out var count))
|
||||||
|
{
|
||||||
|
cartItems.TryAdd(id, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ItemStats
|
public class ItemStats
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
[JsonExtensionData] public Dictionary<string, JToken> AdditionalData { get; set; }
|
[JsonExtensionData] public Dictionary<string, JToken> AdditionalData { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class InvoiceMetadata
|
public class InvoiceMetadata : IHasAdditionalData
|
||||||
{
|
{
|
||||||
public static readonly JsonSerializer MetadataSerializer;
|
public static readonly JsonSerializer MetadataSerializer;
|
||||||
static InvoiceMetadata()
|
static InvoiceMetadata()
|
||||||
@@ -45,165 +45,167 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string OrderId
|
public string OrderId
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("orderId");
|
get => this.GetAdditionalData<string>("orderId");
|
||||||
set => SetMetadata("orderId", value);
|
set => this.SetAdditionalData("orderId", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string OrderUrl
|
public string OrderUrl
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("orderUrl");
|
get => this.GetAdditionalData<string>("orderUrl");
|
||||||
set => SetMetadata("orderUrl", value);
|
set => this.SetAdditionalData("orderUrl", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string PaymentRequestId
|
public string PaymentRequestId
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("paymentRequestId");
|
get => this.GetAdditionalData<string>("paymentRequestId");
|
||||||
set => SetMetadata("paymentRequestId", value);
|
set => this.SetAdditionalData("paymentRequestId", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerName
|
public string BuyerName
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerName");
|
get => this.GetAdditionalData<string>("buyerName");
|
||||||
set => SetMetadata("buyerName", value);
|
set => this.SetAdditionalData("buyerName", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerEmail
|
public string BuyerEmail
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerEmail");
|
get => this.GetAdditionalData<string>("buyerEmail");
|
||||||
set => SetMetadata("buyerEmail", value);
|
set => this.SetAdditionalData("buyerEmail", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerCountry
|
public string BuyerCountry
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerCountry");
|
get => this.GetAdditionalData<string>("buyerCountry");
|
||||||
set => SetMetadata("buyerCountry", value);
|
set => this.SetAdditionalData("buyerCountry", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerZip
|
public string BuyerZip
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerZip");
|
get => this.GetAdditionalData<string>("buyerZip");
|
||||||
set => SetMetadata("buyerZip", value);
|
set => this.SetAdditionalData("buyerZip", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerState
|
public string BuyerState
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerState");
|
get => this.GetAdditionalData<string>("buyerState");
|
||||||
set => SetMetadata("buyerState", value);
|
set => this.SetAdditionalData("buyerState", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerCity
|
public string BuyerCity
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerCity");
|
get => this.GetAdditionalData<string>("buyerCity");
|
||||||
set => SetMetadata("buyerCity", value);
|
set => this.SetAdditionalData("buyerCity", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerAddress2
|
public string BuyerAddress2
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerAddress2");
|
get => this.GetAdditionalData<string>("buyerAddress2");
|
||||||
set => SetMetadata("buyerAddress2", value);
|
set => this.SetAdditionalData("buyerAddress2", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerAddress1
|
public string BuyerAddress1
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerAddress1");
|
get => this.GetAdditionalData<string>("buyerAddress1");
|
||||||
set => SetMetadata("buyerAddress1", value);
|
set => this.SetAdditionalData("buyerAddress1", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BuyerPhone
|
public string BuyerPhone
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("buyerPhone");
|
get => this.GetAdditionalData<string>("buyerPhone");
|
||||||
set => SetMetadata("buyerPhone", value);
|
set => this.SetAdditionalData("buyerPhone", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string ItemDesc
|
public string ItemDesc
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("itemDesc");
|
get => this.GetAdditionalData<string>("itemDesc");
|
||||||
set => SetMetadata("itemDesc", value);
|
set => this.SetAdditionalData("itemDesc", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string ItemCode
|
public string ItemCode
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("itemCode");
|
get => this.GetAdditionalData<string>("itemCode");
|
||||||
set => SetMetadata("itemCode", value);
|
set => this.SetAdditionalData("itemCode", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool? Physical
|
public bool? Physical
|
||||||
{
|
{
|
||||||
get => GetMetadata<bool?>("physical");
|
get => this.GetAdditionalData<bool?>("physical");
|
||||||
set => SetMetadata("physical", value);
|
set => this.SetAdditionalData("physical", value);
|
||||||
}
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public decimal? TaxIncluded
|
public decimal? TaxIncluded
|
||||||
{
|
{
|
||||||
get => GetMetadata<decimal?>("taxIncluded");
|
get => this.GetAdditionalData<decimal?>("taxIncluded");
|
||||||
set => SetMetadata("taxIncluded", value);
|
set => this.SetAdditionalData("taxIncluded", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// posData is a field that may be treated differently for presentation and in some legacy API
|
||||||
|
/// Before, it was a string field which could contain some JSON data inside.
|
||||||
|
/// For making it easier to query on the DB, and for logic using PosData in the code, we decided to
|
||||||
|
/// parse it as a JObject.
|
||||||
|
///
|
||||||
|
/// This property will return the posData as a JObject, even if it's a Json string inside.
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string PosData
|
public JObject PosData
|
||||||
{
|
{
|
||||||
get => GetMetadata<string>("posData");
|
get
|
||||||
set => SetMetadata("posData", value);
|
{
|
||||||
|
if (AdditionalData == null || !(AdditionalData.TryGetValue("posData", out var jt) is true))
|
||||||
|
return default;
|
||||||
|
if (jt.Type == JTokenType.Null)
|
||||||
|
return default;
|
||||||
|
if (jt.Type == JTokenType.String)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JObject.Parse(jt.Value<string>());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (jt.Type == JTokenType.Object)
|
||||||
|
return (JObject)jt;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.SetAdditionalData<JObject>("posData", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See comments on <see cref="PosData"/>
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public string PosDataLegacy
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetAdditionalData<string>("posData");
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PosData = JObject.Parse(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.SetAdditionalData<string>("posData", value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
[JsonExtensionData]
|
[JsonExtensionData]
|
||||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||||
|
|
||||||
public T GetMetadata<T>(string propName)
|
|
||||||
{
|
|
||||||
if (AdditionalData == null || !(AdditionalData.TryGetValue(propName, out var jt) is true))
|
|
||||||
return default;
|
|
||||||
if (jt.Type == JTokenType.Null)
|
|
||||||
return default;
|
|
||||||
if (typeof(T) == typeof(string))
|
|
||||||
{
|
|
||||||
return (T)(object)jt.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return jt.Value<T>();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void SetMetadata<T>(string propName, T value)
|
|
||||||
{
|
|
||||||
JToken data;
|
|
||||||
if (typeof(T) == typeof(string) && value is string v)
|
|
||||||
{
|
|
||||||
data = new JValue(v);
|
|
||||||
AdditionalData ??= new Dictionary<string, JToken>();
|
|
||||||
AdditionalData.AddOrReplace(propName, data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value is null)
|
|
||||||
{
|
|
||||||
AdditionalData?.Remove(propName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (value is string s)
|
|
||||||
{
|
|
||||||
data = JToken.Parse(s);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data = JToken.FromObject(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
data = JToken.FromObject(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
AdditionalData ??= new Dictionary<string, JToken>();
|
|
||||||
AdditionalData.AddOrReplace(propName, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InvoiceMetadata FromJObject(JObject jObject)
|
public static InvoiceMetadata FromJObject(JObject jObject)
|
||||||
{
|
{
|
||||||
return jObject.ToObject<InvoiceMetadata>(MetadataSerializer);
|
return jObject.ToObject<InvoiceMetadata>(MetadataSerializer);
|
||||||
@@ -214,7 +216,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InvoiceEntity
|
public class InvoiceEntity : IHasAdditionalData
|
||||||
{
|
{
|
||||||
class BuyerInformation
|
class BuyerInformation
|
||||||
{
|
{
|
||||||
@@ -302,11 +304,35 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
.Select(t => t.Substring(prefix.Length)).ToArray();
|
.Select(t => t.Substring(prefix.Length)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use GetDerivationStrategies instead")]
|
|
||||||
public string DerivationStrategy { get; set; }
|
|
||||||
|
|
||||||
[Obsolete("Use GetPaymentMethodFactories() instead")]
|
[Obsolete("Use GetPaymentMethodFactories() instead")]
|
||||||
public string DerivationStrategies { get; set; }
|
[JsonIgnore]
|
||||||
|
public JObject DerivationStrategies
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (AdditionalData is null || AdditionalData.TryGetValue("derivationStrategies", out var v) is not true)
|
||||||
|
{
|
||||||
|
if (AdditionalData is null || AdditionalData.TryGetValue("derivationStrategy", out v) is not true || Networks.BTC is null)
|
||||||
|
return null;
|
||||||
|
// This code is very unlikely called. "derivationStrategy" is an old property that was present in 2018.
|
||||||
|
// And this property is only read for unexpired invoices with lazy payments (Feature unavailable then)
|
||||||
|
var settings = DerivationSchemeSettings.Parse(v.ToString(), Networks.BTC);
|
||||||
|
settings.AccountOriginal = v.ToString();
|
||||||
|
settings.Source = "ManualDerivationScheme";
|
||||||
|
return JObject.Parse(settings.ToJson());
|
||||||
|
}
|
||||||
|
if (v.Type == JTokenType.String)
|
||||||
|
return JObject.Parse(v.Value<string>());
|
||||||
|
if (v.Type == JTokenType.Object)
|
||||||
|
return (JObject)v;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.SetAdditionalData("derivationStrategies", value);
|
||||||
|
this.SetAdditionalData<string>("derivationStrategy", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
public IEnumerable<T> GetSupportedPaymentMethod<T>(PaymentMethodId paymentMethodId) where T : ISupportedPaymentMethod
|
public IEnumerable<T> GetSupportedPaymentMethod<T>(PaymentMethodId paymentMethodId) where T : ISupportedPaymentMethod
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
@@ -321,11 +347,9 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
public IEnumerable<ISupportedPaymentMethod> GetSupportedPaymentMethod()
|
public IEnumerable<ISupportedPaymentMethod> GetSupportedPaymentMethod()
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
bool btcReturned = false;
|
if (DerivationStrategies != null)
|
||||||
if (!string.IsNullOrEmpty(DerivationStrategies))
|
|
||||||
{
|
{
|
||||||
JObject strategies = JObject.Parse(DerivationStrategies);
|
foreach (var strat in DerivationStrategies.Properties())
|
||||||
foreach (var strat in strategies.Properties())
|
|
||||||
{
|
{
|
||||||
if (!PaymentMethodId.TryParse(strat.Name, out var paymentMethodId))
|
if (!PaymentMethodId.TryParse(strat.Name, out var paymentMethodId))
|
||||||
{
|
{
|
||||||
@@ -334,20 +358,10 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
var network = Networks.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
|
var network = Networks.GetNetwork<BTCPayNetworkBase>(paymentMethodId.CryptoCode);
|
||||||
if (network != null)
|
if (network != null)
|
||||||
{
|
{
|
||||||
if (network == Networks.BTC && paymentMethodId.PaymentType == PaymentTypes.BTCLike)
|
|
||||||
btcReturned = true;
|
|
||||||
yield return paymentMethodId.PaymentType.DeserializeSupportedPaymentMethod(network, strat.Value);
|
yield return paymentMethodId.PaymentType.DeserializeSupportedPaymentMethod(network, strat.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!btcReturned && !string.IsNullOrEmpty(DerivationStrategy))
|
|
||||||
{
|
|
||||||
if (Networks.BTC != null)
|
|
||||||
{
|
|
||||||
yield return BTCPayServer.DerivationSchemeSettings.Parse(DerivationStrategy, Networks.BTC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,10 +372,8 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
{
|
{
|
||||||
obj.Add(strat.PaymentId.ToString(), PaymentMethodExtensions.Serialize(strat));
|
obj.Add(strat.PaymentId.ToString(), PaymentMethodExtensions.Serialize(strat));
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
// This field should eventually disappear
|
|
||||||
DerivationStrategy = null;
|
|
||||||
}
|
}
|
||||||
DerivationStrategies = JsonConvert.SerializeObject(obj);
|
DerivationStrategies = obj;
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,7 +475,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
Id = Id,
|
Id = Id,
|
||||||
StoreId = StoreId,
|
StoreId = StoreId,
|
||||||
OrderId = Metadata.OrderId,
|
OrderId = Metadata.OrderId,
|
||||||
PosData = Metadata.PosData,
|
PosData = Metadata.PosDataLegacy,
|
||||||
CurrentTime = DateTimeOffset.UtcNow,
|
CurrentTime = DateTimeOffset.UtcNow,
|
||||||
InvoiceTime = InvoiceTime,
|
InvoiceTime = InvoiceTime,
|
||||||
ExpirationTime = ExpirationTime,
|
ExpirationTime = ExpirationTime,
|
||||||
@@ -710,7 +722,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
token is JValue val &&
|
token is JValue val &&
|
||||||
val.Type == JTokenType.String)
|
val.Type == JTokenType.String)
|
||||||
{
|
{
|
||||||
wellknown.PosData = val.Value<string>();
|
wellknown.PosDataLegacy = val.Value<string>();
|
||||||
}
|
}
|
||||||
if (AdditionalData.TryGetValue("orderId", out var token2) &&
|
if (AdditionalData.TryGetValue("orderId", out var token2) &&
|
||||||
token2 is JValue val2 &&
|
token2 is JValue val2 &&
|
||||||
|
|||||||
@@ -253,39 +253,32 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column gap-5">
|
<div class="d-flex flex-column gap-5">
|
||||||
@if (Model.PosData.Any())
|
<div>
|
||||||
{
|
<h3 class="mb-3">Product Information</h3>
|
||||||
<div>
|
<table class="table mb-0">
|
||||||
<h3 class="mb-3">Product Information</h3>
|
@if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemCode))
|
||||||
<table class="table mb-0">
|
{
|
||||||
@if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemCode))
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<th class="fw-semibold">Item code</th>
|
|
||||||
<td>@Model.TypedMetadata.ItemCode</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
@if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemDesc))
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<th class="fw-semibold">Item Description</th>
|
|
||||||
<td>@Model.TypedMetadata.ItemDesc</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th class="fw-semibold">Price</th>
|
<th class="fw-semibold">Item code</th>
|
||||||
<td>@Model.Fiat</td>
|
<td>@Model.TypedMetadata.ItemCode</td>
|
||||||
</tr>
|
</tr>
|
||||||
@if (Model.TaxIncluded is not null)
|
}
|
||||||
{
|
@if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemDesc))
|
||||||
<tr>
|
{
|
||||||
<th class="fw-semibold">Tax Included</th>
|
<tr>
|
||||||
<td>@Model.TaxIncluded</td>
|
<th class="fw-semibold">Item Description</th>
|
||||||
</tr>
|
<td>@Model.TypedMetadata.ItemDesc</td>
|
||||||
}
|
</tr>
|
||||||
</table>
|
}
|
||||||
</div>
|
@if (Model.TaxIncluded is not null)
|
||||||
}
|
{
|
||||||
|
<tr>
|
||||||
|
<th class="fw-semibold">Tax Included</th>
|
||||||
|
<td>@Model.TaxIncluded</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@if (Model.TypedMetadata.BuyerName is not null ||
|
@if (Model.TypedMetadata.BuyerName is not null ||
|
||||||
Model.TypedMetadata.BuyerEmail is not null ||
|
Model.TypedMetadata.BuyerEmail is not null ||
|
||||||
Model.TypedMetadata.BuyerPhone is not null ||
|
Model.TypedMetadata.BuyerPhone is not null ||
|
||||||
|
|||||||
Reference in New Issue
Block a user