mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-09 16:04:43 +01:00
Introduce Payout metadata for api and plugins (#5182)
* Introduce Payout metadata for api and plugins * fix controller * fix metadata requirement * save an object * pr changes
This commit is contained in:
parent
dc986959fd
commit
36ea17a6b7
11 changed files with 120 additions and 9 deletions
|
@ -1,8 +1,11 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Client.Models;
|
namespace BTCPayServer.Client.Models;
|
||||||
|
|
||||||
public class CreatePayoutThroughStoreRequest : CreatePayoutRequest
|
public class CreatePayoutThroughStoreRequest : CreatePayoutRequest
|
||||||
{
|
{
|
||||||
public string? PullPaymentId { get; set; }
|
public string? PullPaymentId { get; set; }
|
||||||
public bool? Approved { get; set; }
|
public bool? Approved { get; set; }
|
||||||
|
public JObject? Metadata { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,5 +31,6 @@ namespace BTCPayServer.Client.Models
|
||||||
public PayoutState State { get; set; }
|
public PayoutState State { get; set; }
|
||||||
public int Revision { get; set; }
|
public int Revision { get; set; }
|
||||||
public JObject PaymentProof { get; set; }
|
public JObject PaymentProof { get; set; }
|
||||||
|
public JObject Metadata { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1152,7 +1152,8 @@ namespace BTCPayServer.Tests
|
||||||
Approved = false,
|
Approved = false,
|
||||||
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 () =>
|
||||||
{
|
{
|
||||||
|
@ -3545,6 +3546,7 @@ namespace BTCPayServer.Tests
|
||||||
PaymentMethod = "BTC_LightningNetwork",
|
PaymentMethod = "BTC_LightningNetwork",
|
||||||
Destination = customerInvoice.BOLT11
|
Destination = customerInvoice.BOLT11
|
||||||
});
|
});
|
||||||
|
Assert.Equal(payout.Metadata.ToString(), new JObject().ToString()); //empty
|
||||||
Assert.Empty(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork"));
|
Assert.Empty(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork"));
|
||||||
await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork",
|
await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork",
|
||||||
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600) });
|
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600) });
|
||||||
|
@ -3555,6 +3557,36 @@ namespace BTCPayServer.Tests
|
||||||
(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(PayoutState.Completed, payoutC.State);
|
Assert.Equal(PayoutState.Completed, payoutC.State);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
payout = await adminClient.CreatePayout(admin.StoreId,
|
||||||
|
new CreatePayoutThroughStoreRequest()
|
||||||
|
{
|
||||||
|
Approved = true,
|
||||||
|
PaymentMethod = "BTC",
|
||||||
|
Destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString(),
|
||||||
|
Amount = 0.0001m,
|
||||||
|
Metadata = JObject.FromObject(new
|
||||||
|
{
|
||||||
|
source ="apitest",
|
||||||
|
sourceLink = "https://chocolate.com"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
|
||||||
|
{
|
||||||
|
source = "apitest",
|
||||||
|
sourceLink = "https://chocolate.com"
|
||||||
|
}).ToString());
|
||||||
|
|
||||||
|
payout =
|
||||||
|
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
|
||||||
|
|
||||||
|
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
|
||||||
|
{
|
||||||
|
source = "apitest",
|
||||||
|
sourceLink = "https://chocolate.com"
|
||||||
|
}).ToString());
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Timeout = 60 * 2 * 1000)]
|
[Fact(Timeout = 60 * 2 * 1000)]
|
||||||
|
|
|
@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
|
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.Greenfield
|
namespace BTCPayServer.Controllers.Greenfield
|
||||||
|
@ -284,7 +285,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
Amount = blob.Amount,
|
Amount = blob.Amount,
|
||||||
PaymentMethodAmount = blob.CryptoAmount,
|
PaymentMethodAmount = blob.CryptoAmount,
|
||||||
Revision = blob.Revision,
|
Revision = blob.Revision,
|
||||||
State = p.State
|
State = p.State,
|
||||||
|
Metadata = blob.Metadata?? new JObject(),
|
||||||
};
|
};
|
||||||
model.Destination = blob.Destination;
|
model.Destination = blob.Destination;
|
||||||
model.PaymentMethod = p.PaymentMethodId;
|
model.PaymentMethod = p.PaymentMethodId;
|
||||||
|
@ -341,7 +343,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
Destination = destination.destination,
|
Destination = destination.destination,
|
||||||
PullPaymentId = pullPaymentId,
|
PullPaymentId = pullPaymentId,
|
||||||
Value = request.Amount,
|
Value = request.Amount,
|
||||||
PaymentMethodId = paymentMethodId,
|
PaymentMethodId = paymentMethodId
|
||||||
});
|
});
|
||||||
|
|
||||||
return HandleClaimResult(result);
|
return HandleClaimResult(result);
|
||||||
|
@ -415,7 +417,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
PreApprove = request.Approved,
|
PreApprove = request.Approved,
|
||||||
Value = request.Amount,
|
Value = request.Amount,
|
||||||
PaymentMethodId = paymentMethodId,
|
PaymentMethodId = paymentMethodId,
|
||||||
StoreId = storeId
|
StoreId = storeId,
|
||||||
|
Metadata = request.Metadata
|
||||||
});
|
});
|
||||||
return HandleClaimResult(result);
|
return HandleClaimResult(result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
|
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
|
||||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||||
using PullPaymentData = BTCPayServer.Data.PullPaymentData;
|
using PullPaymentData = BTCPayServer.Data.PullPaymentData;
|
||||||
|
@ -529,10 +530,32 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
var ppBlob = item.PullPayment?.GetBlob();
|
var ppBlob = item.PullPayment?.GetBlob();
|
||||||
var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings);
|
var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings);
|
||||||
|
string payoutSource;
|
||||||
|
if (payoutBlob.Metadata?.TryGetValue("source", StringComparison.InvariantCultureIgnoreCase,
|
||||||
|
out var source) is true)
|
||||||
|
{
|
||||||
|
payoutSource = source.Value<string>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
payoutSource = ppBlob?.Name ?? item.PullPayment?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
string payoutSourceLink = null;
|
||||||
|
if (payoutBlob.Metadata?.TryGetValue("sourceLink", StringComparison.InvariantCultureIgnoreCase,
|
||||||
|
out var sourceLink) is true)
|
||||||
|
{
|
||||||
|
payoutSourceLink = sourceLink.Value<string>();
|
||||||
|
}
|
||||||
|
else if(item.PullPayment?.Id is not null)
|
||||||
|
{
|
||||||
|
payoutSourceLink = Url.Action("ViewPullPayment", "UIPullPayment", new { pullPaymentId = item.PullPayment?.Id });
|
||||||
|
}
|
||||||
var m = new PayoutsModel.PayoutModel
|
var m = new PayoutsModel.PayoutModel
|
||||||
{
|
{
|
||||||
PullPaymentId = item.PullPayment?.Id,
|
PullPaymentId = item.PullPayment?.Id,
|
||||||
PullPaymentName = ppBlob?.Name ?? item.PullPayment?.Id,
|
Source = payoutSource,
|
||||||
|
SourceLink = payoutSourceLink,
|
||||||
Date = item.Payout.Date,
|
Date = item.Payout.Date,
|
||||||
PayoutId = item.Payout.Id,
|
PayoutId = item.Payout.Id,
|
||||||
Amount = _displayFormatter.Currency(payoutBlob.Amount, ppBlob?.Currency ?? PaymentMethodId.Parse(item.Payout.PaymentMethodId).CryptoCode),
|
Amount = _displayFormatter.Currency(payoutBlob.Amount, ppBlob?.Currency ?? PaymentMethodId.Parse(item.Payout.PaymentMethodId).CryptoCode),
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using BTCPayServer.JsonConverters;
|
using BTCPayServer.JsonConverters;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
|
@ -12,5 +14,10 @@ namespace BTCPayServer.Data
|
||||||
public int MinimumConfirmation { get; set; } = 1;
|
public int MinimumConfirmation { get; set; } = 1;
|
||||||
public string Destination { get; set; }
|
public string Destination { get; set; }
|
||||||
public int Revision { get; set; }
|
public int Revision { get; set; }
|
||||||
|
|
||||||
|
[JsonExtensionData]
|
||||||
|
public Dictionary<string, JToken> AdditionalData { get; set; } = new();
|
||||||
|
|
||||||
|
public JObject Metadata { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,9 @@ namespace BTCPayServer.Data
|
||||||
|
|
||||||
public static PayoutBlob GetBlob(this PayoutData data, BTCPayNetworkJsonSerializerSettings serializers)
|
public static PayoutBlob GetBlob(this PayoutData data, BTCPayNetworkJsonSerializerSettings serializers)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<PayoutBlob>(Encoding.UTF8.GetString(data.Blob), serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode));
|
var result = JsonConvert.DeserializeObject<PayoutBlob>(Encoding.UTF8.GetString(data.Blob), serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode));
|
||||||
|
result.Metadata ??= new JObject();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
public static void SetBlob(this PayoutData data, PayoutBlob blob, BTCPayNetworkJsonSerializerSettings serializers)
|
public static void SetBlob(this PayoutData data, PayoutBlob blob, BTCPayNetworkJsonSerializerSettings serializers)
|
||||||
{
|
{
|
||||||
|
|
|
@ -597,7 +597,8 @@ namespace BTCPayServer.HostedServices
|
||||||
var payoutBlob = new PayoutBlob()
|
var payoutBlob = new PayoutBlob()
|
||||||
{
|
{
|
||||||
Amount = claimed,
|
Amount = claimed,
|
||||||
Destination = req.ClaimRequest.Destination.ToString()
|
Destination = req.ClaimRequest.Destination.ToString(),
|
||||||
|
Metadata = req.ClaimRequest.Metadata?? new JObject(),
|
||||||
};
|
};
|
||||||
payout.SetBlob(payoutBlob, _jsonSerializerSettings);
|
payout.SetBlob(payoutBlob, _jsonSerializerSettings);
|
||||||
await ctx.Payouts.AddAsync(payout);
|
await ctx.Payouts.AddAsync(payout);
|
||||||
|
@ -890,6 +891,7 @@ namespace BTCPayServer.HostedServices
|
||||||
public IClaimDestination Destination { get; set; }
|
public IClaimDestination Destination { get; set; }
|
||||||
public string StoreId { get; set; }
|
public string StoreId { get; set; }
|
||||||
public bool? PreApprove { get; set; }
|
public bool? PreApprove { get; set; }
|
||||||
|
public JObject Metadata { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public record PayoutEvent(PayoutEvent.PayoutEventType Type,PayoutData Payout)
|
public record PayoutEvent(PayoutEvent.PayoutEventType Type,PayoutData Payout)
|
||||||
|
|
|
@ -26,7 +26,8 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||||
public bool Selected { get; set; }
|
public bool Selected { get; set; }
|
||||||
public DateTimeOffset Date { get; set; }
|
public DateTimeOffset Date { get; set; }
|
||||||
public string PullPaymentId { get; set; }
|
public string PullPaymentId { get; set; }
|
||||||
public string PullPaymentName { get; set; }
|
public string Source { get; set; }
|
||||||
|
public string SourceLink { get; set; }
|
||||||
public string Destination { get; set; }
|
public string Destination { get; set; }
|
||||||
public string Amount { get; set; }
|
public string Amount { get; set; }
|
||||||
public string ProofLink { get; set; }
|
public string ProofLink { get; set; }
|
||||||
|
|
|
@ -196,7 +196,14 @@
|
||||||
<span>@pp.Date.ToBrowserDate()</span>
|
<span>@pp.Date.ToBrowserDate()</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="mw-100">
|
<td class="mw-100">
|
||||||
<span>@pp.PullPaymentName</span>
|
@if (pp.SourceLink is not null && pp.Source is not null)
|
||||||
|
{
|
||||||
|
<a href="@pp.SourceLink" rel="noreferrer noopener">@pp.Source</a>
|
||||||
|
}
|
||||||
|
else if (pp.Source is not null)
|
||||||
|
{
|
||||||
|
<span>@pp.Source</span>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
<td title="@pp.Destination">
|
<td title="@pp.Destination">
|
||||||
<span class="text-break">@pp.Destination</span>
|
<span class="text-break">@pp.Destination</span>
|
||||||
|
|
|
@ -904,6 +904,10 @@
|
||||||
"approved": {
|
"approved": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether to approve this payout automatically upon creation"
|
"description": "Whether to approve this payout automatically upon creation"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Additional metadata to store with the payout"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1012,6 +1016,32 @@
|
||||||
},
|
},
|
||||||
"paymentProof": {
|
"paymentProof": {
|
||||||
"$ref": "#/components/schemas/PayoutPaymentProof"
|
"$ref": "#/components/schemas/PayoutPaymentProof"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"description": "Additional information around the payout that can be supplied. The mentioned properties are all optional and you can introduce any json format you wish.",
|
||||||
|
"example": {
|
||||||
|
"source": "Payout created through the API"
|
||||||
|
},
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"title": "General information",
|
||||||
|
"properties": {
|
||||||
|
"source": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "The source of the payout creation. Shown on the payout list page."
|
||||||
|
},
|
||||||
|
"sourceLink": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "url",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "A link to the source of the payout creation. Shown on the payout list page."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue