mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Fix: Do not expose xpub without modify store permission (#6212)
This commit is contained in:
parent
272cc3d3c9
commit
9ba4b030ed
@ -46,9 +46,15 @@ public partial class BTCPayServerClient
|
|||||||
return await SendHttpRequest<InvoiceData>($"api/v1/stores/{storeId}/invoices/{invoiceId}", null, HttpMethod.Get, token);
|
return await SendHttpRequest<InvoiceData>($"api/v1/stores/{storeId}/invoices/{invoiceId}", null, HttpMethod.Get, token);
|
||||||
}
|
}
|
||||||
public virtual async Task<InvoicePaymentMethodDataModel[]> GetInvoicePaymentMethods(string storeId, string invoiceId,
|
public virtual async Task<InvoicePaymentMethodDataModel[]> GetInvoicePaymentMethods(string storeId, string invoiceId,
|
||||||
|
bool onlyAccountedPayments = true, bool includeSensitive = false,
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return await SendHttpRequest<InvoicePaymentMethodDataModel[]>($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods", null, HttpMethod.Get, token);
|
var queryPayload = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ nameof(onlyAccountedPayments), onlyAccountedPayments },
|
||||||
|
{ nameof(includeSensitive), includeSensitive }
|
||||||
|
};
|
||||||
|
return await SendHttpRequest<InvoicePaymentMethodDataModel[]>($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods", queryPayload, HttpMethod.Get, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task ArchiveInvoice(string storeId, string invoiceId,
|
public virtual async Task ArchiveInvoice(string storeId, string invoiceId,
|
||||||
|
@ -2421,6 +2421,14 @@ namespace BTCPayServer.Tests
|
|||||||
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);
|
||||||
method = methods.First();
|
method = methods.First();
|
||||||
|
Assert.Equal(JTokenType.Null, method.AdditionalData["accountDerivation"].Type);
|
||||||
|
Assert.NotNull(method.AdditionalData["keyPath"]);
|
||||||
|
|
||||||
|
methods = await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id, includeSensitive: true);
|
||||||
|
method = methods.First();
|
||||||
|
Assert.Equal(JTokenType.String, method.AdditionalData["accountDerivation"].Type);
|
||||||
|
var clientViewOnly = await user.CreateClient(Policies.CanViewInvoices);
|
||||||
|
await AssertApiError(403, "missing-permission", () => clientViewOnly.GetInvoicePaymentMethods(user.StoreId, invoice.Id, includeSensitive: true));
|
||||||
|
|
||||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -14,6 +15,7 @@ using BTCPayServer.Payments;
|
|||||||
using BTCPayServer.Payments.Bitcoin;
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Payouts;
|
using BTCPayServer.Payouts;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Security.Greenfield;
|
using BTCPayServer.Security.Greenfield;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
@ -25,6 +27,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json.Linq;
|
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;
|
||||||
@ -96,11 +99,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[FromQuery] int? take = null
|
[FromQuery] int? take = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData()!;
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return StoreNotFound();
|
|
||||||
}
|
|
||||||
if (startDate is DateTimeOffset s &&
|
if (startDate is DateTimeOffset s &&
|
||||||
endDate is DateTimeOffset e &&
|
endDate is DateTimeOffset e &&
|
||||||
s > e)
|
s > e)
|
||||||
@ -133,17 +132,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
|
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
|
||||||
public async Task<IActionResult> GetInvoice(string storeId, string invoiceId)
|
public async Task<IActionResult> GetInvoice(string storeId, string invoiceId)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
if (invoice?.StoreId != store.Id)
|
if (!BelongsToThisStore(invoice))
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
return InvoiceNotFound();
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(ToModel(invoice));
|
return Ok(ToModel(invoice));
|
||||||
}
|
}
|
||||||
@ -153,16 +144,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[HttpDelete("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
|
[HttpDelete("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
|
||||||
public async Task<IActionResult> ArchiveInvoice(string storeId, string invoiceId)
|
public async Task<IActionResult> ArchiveInvoice(string storeId, string invoiceId)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
|
||||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
if (invoice?.StoreId != store.Id)
|
if (!BelongsToThisStore(invoice))
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
return InvoiceNotFound();
|
||||||
}
|
|
||||||
await _invoiceRepository.ToggleInvoiceArchival(invoiceId, true, storeId);
|
await _invoiceRepository.ToggleInvoiceArchival(invoiceId, true, storeId);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@ -172,19 +156,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[HttpPut("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
|
[HttpPut("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
|
||||||
public async Task<IActionResult> UpdateInvoice(string storeId, string invoiceId, UpdateInvoiceRequest request)
|
public async Task<IActionResult> UpdateInvoice(string storeId, string invoiceId, UpdateInvoiceRequest request)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _invoiceRepository.UpdateInvoiceMetadata(invoiceId, storeId, request.Metadata);
|
var result = await _invoiceRepository.UpdateInvoiceMetadata(invoiceId, storeId, request.Metadata);
|
||||||
if (result != null)
|
if (!BelongsToThisStore(result))
|
||||||
{
|
return InvoiceNotFound();
|
||||||
return Ok(ToModel(result));
|
return Ok(ToModel(result));
|
||||||
}
|
|
||||||
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanCreateInvoice,
|
[Authorize(Policy = Policies.CanCreateInvoice,
|
||||||
@ -192,12 +167,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[HttpPost("~/api/v1/stores/{storeId}/invoices")]
|
[HttpPost("~/api/v1/stores/{storeId}/invoices")]
|
||||||
public async Task<IActionResult> CreateInvoice(string storeId, CreateInvoiceRequest request)
|
public async Task<IActionResult> CreateInvoice(string storeId, CreateInvoiceRequest request)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData()!;
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return StoreNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Amount < 0.0m)
|
if (request.Amount < 0.0m)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(request.Amount), "The amount should be 0 or more.");
|
ModelState.AddModelError(nameof(request.Amount), "The amount should be 0 or more.");
|
||||||
@ -271,17 +241,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
public async Task<IActionResult> MarkInvoiceStatus(string storeId, string invoiceId,
|
public async Task<IActionResult> MarkInvoiceStatus(string storeId, string invoiceId,
|
||||||
MarkInvoiceStatusRequest request)
|
MarkInvoiceStatusRequest request)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
if (invoice.StoreId != store.Id)
|
if (!BelongsToThisStore(invoice))
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
return InvoiceNotFound();
|
||||||
}
|
|
||||||
|
|
||||||
if (!await _invoiceRepository.MarkInvoiceStatus(invoice.Id, request.Status))
|
if (!await _invoiceRepository.MarkInvoiceStatus(invoice.Id, request.Status))
|
||||||
{
|
{
|
||||||
@ -300,17 +262,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[HttpPost("~/api/v1/stores/{storeId}/invoices/{invoiceId}/unarchive")]
|
[HttpPost("~/api/v1/stores/{storeId}/invoices/{invoiceId}/unarchive")]
|
||||||
public async Task<IActionResult> UnarchiveInvoice(string storeId, string invoiceId)
|
public async Task<IActionResult> UnarchiveInvoice(string storeId, string invoiceId)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
if (invoice.StoreId != store.Id)
|
if (!BelongsToThisStore(invoice))
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
return InvoiceNotFound();
|
||||||
}
|
|
||||||
|
|
||||||
if (!invoice.Archived)
|
if (!invoice.Archived)
|
||||||
{
|
{
|
||||||
@ -328,21 +282,23 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[Authorize(Policy = Policies.CanViewInvoices,
|
[Authorize(Policy = Policies.CanViewInvoices,
|
||||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods")]
|
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods")]
|
||||||
public async Task<IActionResult> GetInvoicePaymentMethods(string storeId, string invoiceId, bool onlyAccountedPayments = true)
|
public async Task<IActionResult> GetInvoicePaymentMethods(string storeId, string invoiceId, bool onlyAccountedPayments = true, bool includeSensitive = false)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
if (invoice?.StoreId != store.Id)
|
if (!BelongsToThisStore(invoice))
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
return InvoiceNotFound();
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(ToPaymentMethodModels(invoice, onlyAccountedPayments));
|
if (includeSensitive && !await _authorizationService.CanModifyStore(User))
|
||||||
|
return this.CreateAPIPermissionError(Policies.CanModifyStoreSettings);
|
||||||
|
|
||||||
|
return Ok(ToPaymentMethodModels(invoice, onlyAccountedPayments, includeSensitive));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BelongsToThisStore([NotNullWhen(true)] InvoiceEntity invoice) => BelongsToThisStore(invoice, out _);
|
||||||
|
private bool BelongsToThisStore([NotNullWhen(true)] InvoiceEntity invoice, [MaybeNullWhen(false)] out Data.StoreData store)
|
||||||
|
{
|
||||||
|
store = this.HttpContext.GetStoreData();
|
||||||
|
return invoice?.StoreId is not null && store.Id == invoice.StoreId;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanViewInvoices,
|
[Authorize(Policy = Policies.CanViewInvoices,
|
||||||
@ -350,17 +306,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
[HttpPost("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods/{paymentMethod}/activate")]
|
[HttpPost("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods/{paymentMethod}/activate")]
|
||||||
public async Task<IActionResult> ActivateInvoicePaymentMethod(string storeId, string invoiceId, string paymentMethod)
|
public async Task<IActionResult> ActivateInvoicePaymentMethod(string storeId, string invoiceId, string paymentMethod)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
if (invoice?.StoreId != store.Id)
|
if (!BelongsToThisStore(invoice))
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
return InvoiceNotFound();
|
||||||
}
|
|
||||||
|
|
||||||
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
|
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
|
||||||
{
|
{
|
||||||
@ -381,22 +329,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
return StoreNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
if (invoice == null)
|
if (!BelongsToThisStore(invoice, out var store))
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
return InvoiceNotFound();
|
||||||
}
|
|
||||||
|
|
||||||
if (invoice.StoreId != store.Id)
|
|
||||||
{
|
|
||||||
return InvoiceNotFound();
|
|
||||||
}
|
|
||||||
if (!invoice.GetInvoiceState().CanRefund())
|
if (!invoice.GetInvoiceState().CanRefund())
|
||||||
{
|
{
|
||||||
return this.CreateAPIError("non-refundable", "Cannot refund this invoice");
|
return this.CreateAPIError("non-refundable", "Cannot refund this invoice");
|
||||||
@ -588,12 +523,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
{
|
{
|
||||||
return this.CreateAPIError(404, "invoice-not-found", "The invoice was not found");
|
return this.CreateAPIError(404, "invoice-not-found", "The invoice was not found");
|
||||||
}
|
}
|
||||||
private IActionResult StoreNotFound()
|
|
||||||
{
|
|
||||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity, bool includeAccountedPaymentOnly)
|
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity, bool includeAccountedPaymentOnly, bool includeSensitive)
|
||||||
{
|
{
|
||||||
return entity.GetPaymentPrompts().Select(
|
return entity.GetPaymentPrompts().Select(
|
||||||
prompt =>
|
prompt =>
|
||||||
@ -606,7 +537,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
|
|
||||||
var details = prompt.Details;
|
var details = prompt.Details;
|
||||||
if (handler is not null && prompt.Activated)
|
if (handler is not null && prompt.Activated)
|
||||||
details = JToken.FromObject(handler.ParsePaymentPromptDetails(details), handler.Serializer.ForAPI());
|
{
|
||||||
|
var detailsObj = handler.ParsePaymentPromptDetails(details);
|
||||||
|
if (!includeSensitive)
|
||||||
|
handler.StripDetailsForNonOwner(detailsObj);
|
||||||
|
details = JToken.FromObject(detailsObj, handler.Serializer.ForAPI());
|
||||||
|
}
|
||||||
return new InvoicePaymentMethodDataModel
|
return new InvoicePaymentMethodDataModel
|
||||||
{
|
{
|
||||||
Activated = prompt.Activated,
|
Activated = prompt.Activated,
|
||||||
@ -621,7 +557,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
PaymentMethodFee = accounting?.PaymentMethodFee ?? 0m,
|
PaymentMethodFee = accounting?.PaymentMethodFee ?? 0m,
|
||||||
PaymentLink = (prompt.Activated ? paymentLinkExtension?.GetPaymentLink(prompt, Url) : null) ?? string.Empty,
|
PaymentLink = (prompt.Activated ? paymentLinkExtension?.GetPaymentLink(prompt, Url) : null) ?? string.Empty,
|
||||||
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
|
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList(),
|
||||||
AdditionalData = prompt.Details
|
AdditionalData = details
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
@ -145,9 +145,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
|
|
||||||
if (includeConfig is true)
|
if (includeConfig is true)
|
||||||
{
|
{
|
||||||
var canModifyStore = (await _authorizationService.AuthorizeAsync(User, null,
|
if (!await _authorizationService.CanModifyStore(User))
|
||||||
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;
|
|
||||||
if (!canModifyStore)
|
|
||||||
return this.CreateAPIPermissionError(Policies.CanModifyStoreSettings);
|
return this.CreateAPIPermissionError(Policies.CanModifyStoreSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,10 +831,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<InvoicePaymentMethodDataModel[]> GetInvoicePaymentMethods(string storeId,
|
public override async Task<InvoicePaymentMethodDataModel[]> GetInvoicePaymentMethods(string storeId,
|
||||||
string invoiceId, CancellationToken token = default)
|
string invoiceId,
|
||||||
|
bool onlyAccountedPayments = true, bool includeSensitive = false,
|
||||||
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return GetFromActionResult<InvoicePaymentMethodDataModel[]>(
|
return GetFromActionResult<InvoicePaymentMethodDataModel[]>(
|
||||||
await GetController<GreenfieldInvoiceController>().GetInvoicePaymentMethods(storeId, invoiceId));
|
await GetController<GreenfieldInvoiceController>().GetInvoicePaymentMethods(storeId, invoiceId, onlyAccountedPayments, includeSensitive));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ArchiveInvoice(string storeId, string invoiceId, CancellationToken token = default)
|
public override async Task ArchiveInvoice(string storeId, string invoiceId, CancellationToken token = default)
|
||||||
|
@ -2,6 +2,7 @@ using System.Security.Claims;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Security.Bitpay;
|
using BTCPayServer.Security.Bitpay;
|
||||||
using BTCPayServer.Security.Greenfield;
|
using BTCPayServer.Security.Greenfield;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
@ -12,6 +13,11 @@ namespace BTCPayServer
|
|||||||
{
|
{
|
||||||
public static class AuthorizationExtensions
|
public static class AuthorizationExtensions
|
||||||
{
|
{
|
||||||
|
public static async Task<bool> CanModifyStore(this IAuthorizationService authorizationService, ClaimsPrincipal user)
|
||||||
|
{
|
||||||
|
return (await authorizationService.AuthorizeAsync(user, null,
|
||||||
|
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;
|
||||||
|
}
|
||||||
public static async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet(
|
public static async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet(
|
||||||
this IAuthorizationService authorizationService,
|
this IAuthorizationService authorizationService,
|
||||||
PoliciesSettings policiesSettings,
|
PoliciesSettings policiesSettings,
|
||||||
|
@ -89,7 +89,10 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
{
|
{
|
||||||
return ParsePaymentMethodConfig(config);
|
return ParsePaymentMethodConfig(config);
|
||||||
}
|
}
|
||||||
|
public void StripDetailsForNonOwner(object details)
|
||||||
|
{
|
||||||
|
((BitcoinPaymentPromptDetails)details).AccountDerivation = null;
|
||||||
|
}
|
||||||
public async Task AfterSavingInvoice(PaymentMethodContext paymentMethodContext)
|
public async Task AfterSavingInvoice(PaymentMethodContext paymentMethodContext)
|
||||||
{
|
{
|
||||||
var paymentPrompt = paymentMethodContext.Prompt;
|
var paymentPrompt = paymentMethodContext.Prompt;
|
||||||
|
@ -14,6 +14,9 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public NetworkFeeMode FeeMode { get; set; }
|
public NetworkFeeMode FeeMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The fee rate charged to the user as `PaymentMethodFee`.
|
||||||
|
/// </summary>
|
||||||
[JsonConverter(typeof(NBitcoin.JsonConverters.FeeRateJsonConverter))]
|
[JsonConverter(typeof(NBitcoin.JsonConverters.FeeRateJsonConverter))]
|
||||||
public FeeRate PaymentMethodFeeRate
|
public FeeRate PaymentMethodFeeRate
|
||||||
{
|
{
|
||||||
@ -21,6 +24,10 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public bool PayjoinEnabled { get; set; }
|
public bool PayjoinEnabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The recommended fee rate for this payment method.
|
||||||
|
/// </summary>
|
||||||
[JsonConverter(typeof(NBitcoin.JsonConverters.FeeRateJsonConverter))]
|
[JsonConverter(typeof(NBitcoin.JsonConverters.FeeRateJsonConverter))]
|
||||||
public FeeRate RecommendedFeeRate { get; set; }
|
public FeeRate RecommendedFeeRate { get; set; }
|
||||||
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
|
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
|
||||||
|
@ -67,6 +67,12 @@ namespace BTCPayServer.Payments
|
|||||||
/// <param name="details"></param>
|
/// <param name="details"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
object ParsePaymentPromptDetails(JToken details);
|
object ParsePaymentPromptDetails(JToken details);
|
||||||
|
/// <summary>
|
||||||
|
/// Remove properties from the details which shouldn't appear to non-store owner.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="details">Prompt details</param>
|
||||||
|
void StripDetailsForNonOwner(object details) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parse the configuration of the payment method in the store
|
/// Parse the configuration of the payment method in the store
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -413,6 +413,16 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true
|
"default": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "includeSensitive",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"description": "If `true`, `additionalData` might include sensitive data (such as xpub). Requires the permission `btcpay.store.canmodifystoresettings`.",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "View information about the specified invoice's payment methods",
|
"description": "View information about the specified invoice's payment methods",
|
||||||
@ -644,10 +654,10 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/stores/{storeId}/invoices/{invoiceId}/refund": {
|
"/api/v1/stores/{storeId}/invoices/{invoiceId}/refund": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Invoices"
|
"Invoices"
|
||||||
],
|
],
|
||||||
"summary": "Refund invoice",
|
"summary": "Refund invoice",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -668,69 +678,69 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Refund invoice",
|
"description": "Refund invoice",
|
||||||
"operationId": "Invoices_Refund",
|
"operationId": "Invoices_Refund",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Name of the pull payment (Default: 'Refund' followed by the invoice id)",
|
"description": "Name of the pull payment (Default: 'Refund' followed by the invoice id)",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Description of the pull payment"
|
"description": "Description of the pull payment"
|
||||||
},
|
},
|
||||||
"payoutMethodId": {
|
"payoutMethodId": {
|
||||||
"$ref": "#/components/schemas/PayoutMethodId"
|
"$ref": "#/components/schemas/PayoutMethodId"
|
||||||
},
|
},
|
||||||
"refundVariant": {
|
"refundVariant": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "* `RateThen`: Refund the crypto currency price, at the rate the invoice got paid.\r\n* `CurrentRate`: Refund the crypto currency price, at the current rate.\r\n*`Fiat`: Refund the invoice currency, at the rate when the refund will be sent.\r\n*`OverpaidAmount`: Refund the crypto currency amount that was overpaid.\r\n*`Custom`: Specify the amount, currency, and rate of the refund. (see `customAmount` and `customCurrency`)",
|
"description": "* `RateThen`: Refund the crypto currency price, at the rate the invoice got paid.\r\n* `CurrentRate`: Refund the crypto currency price, at the current rate.\r\n*`Fiat`: Refund the invoice currency, at the rate when the refund will be sent.\r\n*`OverpaidAmount`: Refund the crypto currency amount that was overpaid.\r\n*`Custom`: Specify the amount, currency, and rate of the refund. (see `customAmount` and `customCurrency`)",
|
||||||
"x-enumNames": [
|
"x-enumNames": [
|
||||||
"RateThen",
|
"RateThen",
|
||||||
"CurrentRate",
|
"CurrentRate",
|
||||||
"Fiat",
|
"Fiat",
|
||||||
"Custom"
|
"Custom"
|
||||||
],
|
],
|
||||||
"enum": [
|
"enum": [
|
||||||
"RateThen",
|
"RateThen",
|
||||||
"CurrentRate",
|
"CurrentRate",
|
||||||
"OverpaidAmount",
|
"OverpaidAmount",
|
||||||
"Fiat",
|
"Fiat",
|
||||||
"Custom"
|
"Custom"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"subtractPercentage": {
|
"subtractPercentage": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "decimal",
|
"format": "decimal",
|
||||||
"description": "Optional percentage by which to reduce the refund, e.g. as processing charge or to compensate for the mining fee.",
|
"description": "Optional percentage by which to reduce the refund, e.g. as processing charge or to compensate for the mining fee.",
|
||||||
"example": "2.1"
|
"example": "2.1"
|
||||||
},
|
},
|
||||||
"customAmount": {
|
"customAmount": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "decimal",
|
"format": "decimal",
|
||||||
"description": "The amount to refund if the `refundVariant` is `Custom`.",
|
"description": "The amount to refund if the `refundVariant` is `Custom`.",
|
||||||
"example": "5.00"
|
"example": "5.00"
|
||||||
},
|
},
|
||||||
"customCurrency": {
|
"customCurrency": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The currency to refund if the `refundVariant` is `Custom`",
|
"description": "The currency to refund if the `refundVariant` is `Custom`",
|
||||||
"example": "USD"
|
"example": "USD"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Pull payment for refunding the invoice",
|
"description": "Pull payment for refunding the invoice",
|
||||||
@ -1329,6 +1339,7 @@
|
|||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"title": "*-LNURL",
|
||||||
"description": "LNURL Pay information",
|
"description": "LNURL Pay information",
|
||||||
"properties": {
|
"properties": {
|
||||||
"providedComment": {
|
"providedComment": {
|
||||||
@ -1345,6 +1356,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"title": "*-CHAIN",
|
||||||
|
"description": "Bitcoin On-Chain payment information",
|
||||||
|
"properties": {
|
||||||
|
"keyPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The key path relative to the account derviation key.",
|
||||||
|
"example": "0/1"
|
||||||
|
},
|
||||||
|
"payjoinEnabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If the payjoin feature is enabled for this payment method."
|
||||||
|
},
|
||||||
|
"accountDerivation": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The derivation scheme used to derive addresses (null if `includeSensitive` is `false`)",
|
||||||
|
"example": "xpub6DVMcQAQCtGbNDTEjQGtR1GRoTKw7AzP6bVivX4gFnewcnRk1r1tbczpfsaYjKKVrmtyiwYqAEnALYzZ8yoTArVsKfZekmwLFqQp4MRgPhy"
|
||||||
|
},
|
||||||
|
"recommendedFeeRate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "decimal",
|
||||||
|
"description": "The recommended fee rate for this payment method.",
|
||||||
|
"example": "4.107"
|
||||||
|
},
|
||||||
|
"paymentMethodFeeRate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "decimal",
|
||||||
|
"description": "The fee rate charged to the user as `PaymentMethodFee`.",
|
||||||
|
"example": "3.975"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "No additional information"
|
"description": "No additional information"
|
||||||
|
Loading…
Reference in New Issue
Block a user