fixed send, full transaction list

This commit is contained in:
2025-12-12 08:14:15 +01:00
parent 2947555ef2
commit c8da1cf331
8 changed files with 149 additions and 130 deletions

View File

@@ -5,14 +5,14 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591;CS0618</NoWarn> <NoWarn>$(NoWarn);CS1591;CS0618;CS8073</NoWarn>
</PropertyGroup> </PropertyGroup>
<!-- Plugin specific properties --> <!-- Plugin specific properties -->
<PropertyGroup> <PropertyGroup>
<Product>BreezSpark Lightning Plugin</Product> <Product>BreezSpark Lightning Plugin</Product>
<Description>Nodeless Lightning payments powered by Breez Spark SDK</Description> <Description>Nodeless Lightning payments powered by Breez Nodeless SDK (Spark)</Description>
<Version>1.1.0</Version> <Version>0.0.4</Version>
<Author>Aljaz Ceru</Author> <Author>Aljaz Ceru</Author>
<Company>Aljaz Ceru</Company> <Company>Aljaz Ceru</Company>
<AssemblyName>BTCPayServer.Plugins.BreezSpark</AssemblyName> <AssemblyName>BTCPayServer.Plugins.BreezSpark</AssemblyName>
@@ -40,6 +40,11 @@
<EmbeddedResource Include="Resources\**" /> <EmbeddedResource Include="Resources\**" />
<ProjectReference Include="../btcpayserver/BTCPayServer/BTCPayServer.csproj" /> <ProjectReference Include="../btcpayserver/BTCPayServer/BTCPayServer.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Remove="csharp\**\*.cs" />
<EmbeddedResource Remove="csharp\**\*" />
<None Include="csharp\**\*" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Breez.Sdk.Spark" Version="0.4.1" /> <PackageReference Include="Breez.Sdk.Spark" Version="0.4.1" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />

View File

@@ -18,6 +18,7 @@ using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
@@ -32,18 +33,22 @@ public class BreezSparkController : Controller
private readonly BreezSparkService _breezService; private readonly BreezSparkService _breezService;
private readonly BTCPayWalletProvider _btcWalletProvider; private readonly BTCPayWalletProvider _btcWalletProvider;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly ILogger<BreezSparkController> _logger;
public BreezSparkController( public BreezSparkController(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
BreezSparkService breezService, BreezSparkService breezService,
BTCPayWalletProvider btcWalletProvider, StoreRepository storeRepository) BTCPayWalletProvider btcWalletProvider,
StoreRepository storeRepository,
ILogger<BreezSparkController> logger)
{ {
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary; _paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_breezService = breezService; _breezService = breezService;
_btcWalletProvider = btcWalletProvider; _btcWalletProvider = btcWalletProvider;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_logger = logger;
} }
@@ -176,7 +181,7 @@ public class BreezSparkController : Controller
TempData["bolt11"] = response.paymentRequest; TempData["bolt11"] = response.paymentRequest;
TempData[WellKnownTempData.SuccessMessage] = "Invoice created successfully!"; TempData[WellKnownTempData.SuccessMessage] = "Invoice created successfully!";
return RedirectToAction(nameof(Payments), new {storeId}); return RedirectToAction(nameof(Transactions), new {storeId});
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -203,11 +208,7 @@ public class BreezSparkController : Controller
return RedirectToAction(nameof(Send), new {storeId}); return RedirectToAction(nameof(Send), new {storeId});
} }
BigInteger? amountSats = null; var amountSats = ResolveAmountSats(address, amount);
if (amount > 0)
{
amountSats = new BigInteger(amount.Value);
}
var prepareRequest = new PrepareSendPaymentRequest( var prepareRequest = new PrepareSendPaymentRequest(
paymentRequest: address, paymentRequest: address,
@@ -219,38 +220,30 @@ public class BreezSparkController : Controller
if (prepareResponse.paymentMethod is SendPaymentMethod.Bolt11Invoice bolt11Method) if (prepareResponse.paymentMethod is SendPaymentMethod.Bolt11Invoice bolt11Method)
{ {
var totalFee = bolt11Method.lightningFeeSats + (bolt11Method.sparkTransferFeeSats ?? 0); var totalFee = bolt11Method.lightningFeeSats + (bolt11Method.sparkTransferFeeSats ?? 0);
var viewModel = new var amt = amountSats ?? BigInteger.Zero;
{ ViewData["PaymentDetails"] = new PaymentDetailsDto(
Destination = address, Destination: address,
Amount = amountSats ?? 0, Amount: (long)amt,
Fee = totalFee, Fee: (long)totalFee
PrepareResponseJson = JsonSerializer.Serialize(prepareResponse) );
};
ViewData["PaymentDetails"] = viewModel;
} }
else if (prepareResponse.paymentMethod is SendPaymentMethod.BitcoinAddress bitcoinMethod) else if (prepareResponse.paymentMethod is SendPaymentMethod.BitcoinAddress bitcoinMethod)
{ {
var fees = bitcoinMethod.feeQuote; var fees = bitcoinMethod.feeQuote;
var mediumFee = fees.speedMedium.userFeeSat + fees.speedMedium.l1BroadcastFeeSat; var mediumFee = fees.speedMedium.userFeeSat + fees.speedMedium.l1BroadcastFeeSat;
var viewModel = new ViewData["PaymentDetails"] = new PaymentDetailsDto(
{ Destination: address,
Destination = address, Amount: (long)BigInteger.Abs(amountSats ?? BigInteger.Zero),
Amount = amountSats ?? 0, Fee: (long)mediumFee
Fee = mediumFee, );
PrepareResponseJson = JsonSerializer.Serialize(prepareResponse)
};
ViewData["PaymentDetails"] = viewModel;
} }
else if (prepareResponse.paymentMethod is SendPaymentMethod.SparkAddress sparkMethod) else if (prepareResponse.paymentMethod is SendPaymentMethod.SparkAddress sparkMethod)
{ {
var viewModel = new ViewData["PaymentDetails"] = new PaymentDetailsDto(
{ Destination: address,
Destination = address, Amount: (long)BigInteger.Abs(amountSats ?? BigInteger.Zero),
Amount = amountSats ?? 0, Fee: (long)sparkMethod.fee
Fee = sparkMethod.fee, );
PrepareResponseJson = JsonSerializer.Serialize(prepareResponse)
};
ViewData["PaymentDetails"] = viewModel;
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -263,7 +256,7 @@ public class BreezSparkController : Controller
[HttpPost("confirm-send")] [HttpPost("confirm-send")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ConfirmSend(string storeId, string paymentRequest, long amount, string prepareResponseJson) public async Task<IActionResult> ConfirmSend(string storeId, string paymentRequest, long amount)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
if (client is null) if (client is null)
@@ -273,11 +266,12 @@ public class BreezSparkController : Controller
try try
{ {
var prepareResponse = JsonSerializer.Deserialize<PrepareSendPaymentResponse>(prepareResponseJson); // Re-run preparation to avoid polymorphic JSON deserialization issues
if (prepareResponse == null) var amountSats = ResolveAmountSats(paymentRequest, amount);
{ var prepareResponse = await client.Sdk.PrepareSendPayment(new PrepareSendPaymentRequest(
throw new InvalidOperationException("Invalid payment preparation data"); paymentRequest: paymentRequest,
} amount: amountSats
));
SendPaymentOptions? options = prepareResponse.paymentMethod switch SendPaymentOptions? options = prepareResponse.paymentMethod switch
{ {
@@ -289,7 +283,8 @@ public class BreezSparkController : Controller
confirmationSpeed: OnchainConfirmationSpeed.Medium confirmationSpeed: OnchainConfirmationSpeed.Medium
), ),
SendPaymentMethod.SparkAddress => null, SendPaymentMethod.SparkAddress => null,
_ => throw new NotSupportedException("Unsupported payment method") SendPaymentMethod.SparkInvoice => null,
_ => null
}; };
var sendRequest = new SendPaymentRequest( var sendRequest = new SendPaymentRequest(
@@ -297,14 +292,18 @@ public class BreezSparkController : Controller
options: options options: options
); );
_logger.LogInformation("BreezSpark sending payment for store {StoreId} to {Destination}", storeId, paymentRequest);
var sendResponse = await client.Sdk.SendPayment(sendRequest); var sendResponse = await client.Sdk.SendPayment(sendRequest);
_logger.LogInformation("BreezSpark send complete for store {StoreId}: payment id {PaymentId}, status {Status}",
storeId, sendResponse.payment?.id, sendResponse.payment?.status);
TempData[WellKnownTempData.SuccessMessage] = "Payment sent successfully!"; TempData[WellKnownTempData.SuccessMessage] = "Payment sent successfully!";
return RedirectToAction(nameof(Payments), new {storeId}); return RedirectToAction(nameof(Transactions), new {storeId});
} }
catch (Exception ex) catch (Exception ex)
{ {
TempData[WellKnownTempData.ErrorMessage] = $"Error sending payment: {ex.Message}"; TempData[WellKnownTempData.ErrorMessage] = $"Error sending payment: {ex.Message}";
_logger.LogError(ex, "BreezSpark send failed for store {StoreId}", storeId);
return RedirectToAction(nameof(Send), new {storeId}); return RedirectToAction(nameof(Send), new {storeId});
} }
} }
@@ -484,9 +483,9 @@ public class BreezSparkController : Controller
return NotFound(); return NotFound();
} }
[Route("payments")] [Route("transactions")]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Payments(string storeId, PaymentsViewModel viewModel) public async Task<IActionResult> Transactions(string storeId, PaymentsViewModel viewModel)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
if (client is null) if (client is null)
@@ -495,6 +494,7 @@ public class BreezSparkController : Controller
} }
viewModel ??= new PaymentsViewModel(); viewModel ??= new PaymentsViewModel();
viewModel.Balance = await client.GetBalance();
var req = new ListPaymentsRequest( var req = new ListPaymentsRequest(
typeFilter: null, typeFilter: null,
statusFilter: null, statusFilter: null,
@@ -506,18 +506,75 @@ public class BreezSparkController : Controller
sortAscending: false sortAscending: false
); );
var response = await client.Sdk.ListPayments(req); var response = await client.Sdk.ListPayments(req);
viewModel.Payments = response.payments.Select(client.NormalizePayment).ToList(); var normalized = new List<NormalizedPayment>();
foreach (var p in response.payments.Where(p => p != null))
{
var norm = client.NormalizePayment(p);
if (norm is not null)
{
normalized.Add(norm);
continue;
}
return View(viewModel); // Fallback: show raw SDK payment even if we lack invoice context
long amountSat = 0;
if (p.details is PaymentDetails.Lightning l && !string.IsNullOrEmpty(l.invoice))
{
var nbitcoinNetwork = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC")?.NBitcoinNetwork ?? NBitcoin.Network.Main;
if (BOLT11PaymentRequest.TryParse(l.invoice, out var pr, nbitcoinNetwork) && pr?.MinimumAmount is not null)
{
amountSat = (long)pr.MinimumAmount.ToUnit(LightMoneyUnit.Satoshi);
}
}
long feeSat = 0;
if (p.fees != null)
{
feeSat = (long)(p.fees / 1000);
}
normalized.Add(new NormalizedPayment
{
Id = p.id ?? Guid.NewGuid().ToString("N"),
PaymentType = p.paymentType,
Status = p.status,
Timestamp = p.timestamp,
Amount = LightMoney.Satoshis(amountSat),
Fee = LightMoney.Satoshis(feeSat),
Description = p.details?.ToString() ?? "BreezSpark payment"
});
}
viewModel.Payments = normalized;
return View("Transactions", viewModel);
}
private BigInteger? ResolveAmountSats(string paymentRequest, long? amount)
{
if (amount.HasValue && amount.Value > 0)
{
return new BigInteger(amount.Value);
}
// Try to derive amount from bolt11 invoice if present
var nbitcoinNetwork = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC")?.NBitcoinNetwork ?? NBitcoin.Network.Main;
if (BOLT11PaymentRequest.TryParse(paymentRequest, out var pr, nbitcoinNetwork) && pr?.MinimumAmount is not null)
{
return new BigInteger((long)pr.MinimumAmount.ToUnit(LightMoneyUnit.Satoshi));
}
return null;
} }
} }
public class PaymentsViewModel : BasePagingViewModel public class PaymentsViewModel : BasePagingViewModel
{ {
public List<NormalizedPayment> Payments { get; set; } = new(); public List<NormalizedPayment> Payments { get; set; } = new();
public LightningNodeBalance? Balance { get; set; }
public override int CurrentPageCount => Payments.Count; public override int CurrentPageCount => Payments.Count;
} }
public record PaymentDetailsDto(string Destination, long Amount, long Fee);
// Helper class for swap information display in views // Helper class for swap information display in views
public class SwapInfo public class SwapInfo
{ {

View File

@@ -215,10 +215,6 @@ public class BreezSparkLightningClient : ILightningClient, IDisposable
return new LightningNodeBalance() return new LightningNodeBalance()
{ {
OnchainBalance = new OnchainBalance()
{
Confirmed = Money.Satoshis((long)response.balanceSats)
},
OffchainBalance = new OffchainBalance() OffchainBalance = new OffchainBalance()
{ {
Local = LightMoney.Satoshis((long)response.balanceSats), Local = LightMoney.Satoshis((long)response.balanceSats),
@@ -230,10 +226,6 @@ public class BreezSparkLightningClient : ILightningClient, IDisposable
{ {
return new LightningNodeBalance() return new LightningNodeBalance()
{ {
OnchainBalance = new OnchainBalance()
{
Confirmed = Money.Zero
},
OffchainBalance = new OffchainBalance() OffchainBalance = new OffchainBalance()
{ {
Local = LightMoney.Zero, Local = LightMoney.Zero,

View File

@@ -9,6 +9,7 @@
@using BTCPayServer.Security @using BTCPayServer.Security
@using BTCPayServer.Services @using BTCPayServer.Services
@using BTCPayServer.Services.Invoices @using BTCPayServer.Services.Invoices
@using BTCPayServer.Lightning
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@using NBitcoin @using NBitcoin
@inject BreezSparkService BreezService @inject BreezSparkService BreezService
@@ -22,14 +23,17 @@
StoreDashboardViewModel dashboardModel => dashboardModel.StoreId, StoreDashboardViewModel dashboardModel => dashboardModel.StoreId,
_ => Context.GetImplicitStoreId() _ => Context.GetImplicitStoreId()
}; };
var sdk = BreezService.GetClient(storeId)?.Sdk; var client = BreezService.GetClient(storeId);
if (sdk is null) var sdk = client?.Sdk;
if (client is null || sdk is null)
return; return;
LightningNodeBalance balance = await client.GetBalance();
} }
<div class="row mb-4 mt-4"> <div class="row mb-4 mt-4">
<div class="col-12"> <div class="col-12">
<h3>BreezSpark Lightning Node Information</h3> <h3>BreezSpark Lightning Balance</h3>
@try @try
{ {
@@ -37,39 +41,13 @@
{ {
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h5 class="card-title mb-0">Node Status</h5> <h5 class="card-title mb-0">Balance</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="text-muted"> <h3 class="mb-0">
BreezSpark Lightning Node is connected and operational. @((balance?.OffchainBalance?.Local ?? LightMoney.Zero).ToDecimal(LightMoneyUnit.BTC)) BTC
This is a simplified view for SDK v0.4.1 compatibility. </h3>
</p> <p class="text-muted mb-0">Off-chain balance (Breez Spark)</p>
<div class="row">
<div class="col-md-6">
<dl class="row">
<dt class="col-sm-6">Status</dt>
<dd class="col-sm-6">
<span class="badge bg-success">Connected</span>
</dd>
<dt class="col-sm-6">Network</dt>
<dd class="col-sm-6">Bitcoin</dd>
<dt class="col-sm-6">Type</dt>
<dd class="col-sm-6">Breez Spark SDK v0.4.1</dd>
</dl>
</div>
<div class="col-md-6">
<dl class="row">
<dt class="col-sm-6">Service</dt>
<dd class="col-sm-6">Lightning Network</dd>
<dt class="col-sm-6">Integration</dt>
<dd class="col-sm-6">BTCPay Server</dd>
</dl>
</div>
</div>
</div> </div>
</div> </div>
@@ -80,13 +58,13 @@
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<a href="@Url.Action("SwapIn", "BreezSpark", new { storeId = storeId })" class="btn btn-primary w-100 mb-2"> <a href="@Url.Action("Receive", "BreezSpark", new { storeId = storeId })" class="btn btn-primary w-100 mb-2">
<i class="bi bi-arrow-down-circle"></i> Swap In <i class="bi bi-arrow-down-circle"></i> Receive
</a> </a>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<a href="@Url.Action("SwapOut", "BreezSpark", new { storeId = storeId })" class="btn btn-success w-100 mb-2"> <a href="@Url.Action("Send", "BreezSpark", new { storeId = storeId })" class="btn btn-success w-100 mb-2">
<i class="bi bi-arrow-up-circle"></i> Swap Out <i class="bi bi-arrow-up-circle"></i> Send
</a> </a>
</div> </div>
</div> </div>
@@ -103,4 +81,4 @@
</div> </div>
} }
</div> </div>
</div> </div>

View File

@@ -37,7 +37,7 @@
} }
@if (ViewData["PaymentDetails"] is PaymentDetailsViewModel paymentDetails) @if (ViewData["PaymentDetails"] is BTCPayServer.Plugins.BreezSpark.PaymentDetailsDto paymentDetails)
{ {
<div class="payment-details mb-4"> <div class="payment-details mb-4">
<h4>Payment Details</h4> <h4>Payment Details</h4>
@@ -51,13 +51,12 @@
<p><strong>Total:</strong> @(paymentDetails.Amount + paymentDetails.Fee) sats</p> <p><strong>Total:</strong> @(paymentDetails.Amount + paymentDetails.Fee) sats</p>
</div> </div>
</div> </div>
<form method="post" asp-action="ConfirmSend" asp-route-storeId="@storeId"> <form method="post" asp-action="ConfirmSend" asp-route-storeId="@storeId">
<input type="hidden" name="paymentRequest" value="@paymentDetails.Destination" /> <input type="hidden" name="paymentRequest" value="@paymentDetails.Destination" />
<input type="hidden" name="amount" value="@paymentDetails.Amount" /> <input type="hidden" name="amount" value="@paymentDetails.Amount" />
<input type="hidden" name="prepareResponse" value="@paymentDetails.PrepareResponseJson" /> <button type="submit" class="btn btn-success">Confirm and Send</button>
<button type="submit" class="btn btn-success">Confirm and Send</button> <a href="javascript:history.back()" class="btn btn-secondary">Cancel</a>
<a href="javascript:history.back()" class="btn btn-secondary">Cancel</a> </form>
</form>
</div> </div>
} }
else else
@@ -70,7 +69,7 @@ else
<span>@ViewData["Title"]</span> <span>@ViewData["Title"]</span>
</h3> </h3>
<div class="d-flex gap-3 mt-3 mt-sm-0"> <div class="d-flex gap-3 mt-3 mt-sm-0">
<button type="submit" class="btn btn-primary">Prepare Payment</button> <button type="submit" class="btn btn-primary">Pay</button>
</div> </div>
</div> </div>
<div asp-validation-summary="ModelOnly" class="text-danger"></div> <div asp-validation-summary="ModelOnly" class="text-danger"></div>
@@ -81,20 +80,12 @@ else
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="amount" class="form-label">Amount (sats)</label> <label for="amount" class="form-label">Amount (sats)</label>
<input type="number" id="amount" name="amount" min="1" max="@max" class="form-control" placeholder="Amount in satoshis"/> <input type="number" id="amount" name="amount" min="1" max="@max" class="form-control" placeholder="Leave blank if invoice has amount"/>
<small class="form-text text-muted">Maximum payable: @max sats</small> <small class="form-text text-muted">Maximum payable: @max sats. Bolt11 amounts are auto-read; fill only for zero-amount invoices.</small>
</div> </div>
</div> </div>
</div> </div>
</form> </form>
} }
@functions { @* PaymentDetails model is provided via ViewData from the controller *@
public class PaymentDetailsViewModel
{
public string Destination { get; set; } = string.Empty;
public long Amount { get; set; }
public long Fee { get; set; }
public string PrepareResponseJson { get; set; } = string.Empty;
}
}

View File

@@ -5,11 +5,12 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Components.QRCode @using BTCPayServer.Components.QRCode
@using BTCPayServer.Components.TruncateCenter @using BTCPayServer.Components.TruncateCenter
@using BTCPayServer.Lightning
@model BTCPayServer.Plugins.BreezSpark.PaymentsViewModel @model BTCPayServer.Plugins.BreezSpark.PaymentsViewModel
@{ @{
var storeId = Context.GetCurrentStoreId(); var storeId = Context.GetCurrentStoreId();
ViewData.SetActivePage("BreezSpark", "Payments", "Payments"); ViewData.SetActivePage("BreezSpark", "Transactions", "Transactions");
TempData.TryGetValue("bolt11", out var bolt11); TempData.TryGetValue("bolt11", out var bolt11);
} }
@@ -17,7 +18,7 @@
<div class="col-12"> <div class="col-12">
<div class="d-flex align-items-center justify-content-between mb-3"> <div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0"> <h3 class="mb-0">
<span>@ViewData["Title"]</span> <span>Transactions</span>
</h3> </h3>
<div class="d-flex gap-3 mt-3 mt-sm-0"> <div class="d-flex gap-3 mt-3 mt-sm-0">
@@ -43,7 +44,7 @@
</div> </div>
} }
<partial name="BreezSpark/BreezSparkPaymentsTable" model="Model.Payments"/> <partial name="BreezSpark/BreezSparkTransactionsTable" model="Model.Payments"/>
<vc:pager view-model="Model"></vc:pager> <vc:pager view-model="Model"></vc:pager>
</div> </div>
</div> </div>

View File

@@ -45,7 +45,7 @@
<a permission="@Policies.CanViewStoreSettings" asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.ActivePageClass("BreezSpark", null, "Info")">Info</a> <a permission="@Policies.CanViewStoreSettings" asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.ActivePageClass("BreezSpark", null, "Info")">Info</a>
</li> </li>
<li class="nav-item nav-item-sub"> <li class="nav-item nav-item-sub">
<a permission="@Policies.CanViewStoreSettings" asp-action="Payments" asp-route-storeId="@storeId" class="nav-link @ViewData.ActivePageClass("BreezSpark", null, "Payments")">Payments</a> <a permission="@Policies.CanViewStoreSettings" asp-action="Transactions" asp-route-storeId="@storeId" class="nav-link @ViewData.ActivePageClass("BreezSpark", null, "Transactions")">Transactions</a>
</li> </li>
<li class="nav-item nav-item-sub"> <li class="nav-item nav-item-sub">
<a permission="@Policies.CanModifyStoreSettings" asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.ActivePageClass("BreezSpark", null, "Configure")">Configuration</a> <a permission="@Policies.CanModifyStoreSettings" asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.ActivePageClass("BreezSpark", null, "Configure")">Configuration</a>

View File

@@ -5,7 +5,7 @@
@using BTCPayServer.Security @using BTCPayServer.Security
@model List<NormalizedPayment> @model List<NormalizedPayment>
@{ @{
var data = Model ?? new List<NormalizedPayment>(); var data = (Model ?? new List<NormalizedPayment>()).Where(p => p != null).ToList();
var storeId = Context.GetImplicitStoreId() ?? string.Empty; var storeId = Context.GetImplicitStoreId() ?? string.Empty;
var isDashboard = false; var isDashboard = false;
} }
@@ -20,20 +20,11 @@
</style> </style>
} }
<div id="breezspark-payments" class="@(isDashboard ? "widget store-wallet-balance" : "")"> <div id="breezspark-payments" class="@(isDashboard ? "widget store-wallet-balance" : "")">
@if (isDashboard) @* Dashboard widget header removed for simplicity *@
{
<header>
<h3>BreezSpark Payments</h3>
@if (data.Any())
{
<a asp-controller="BreezSpark" asp-action="Payments" asp-route-storeId="@storeId">View All</a>
}
</header>
}
@if (!data.Any()) @if (!data.Any())
{ {
<p class="text-secondary mt-3 mb-0"> <p class="text-secondary mt-3 mb-0">
There are no recent payments. There are no recent transactions.
</p> </p>
} }
else else
@@ -54,6 +45,10 @@
<tbody> <tbody>
@foreach (var payment in data) @foreach (var payment in data)
{ {
if (payment == null)
{
continue;
}
<tr> <tr>
<td class="smMaxWidth text-truncate"> <td class="smMaxWidth text-truncate">