Generate unique order IDs for PoS and Crowdfund sales (#5127)

* Generate unique order IDs for PoS and Crowdfund sales

Part of #5054.

* Refactorings

* Updates

* Updates

* Refactoring

* Remove search by AdditionalSearchTerm

* Implement appid

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
d11n 2023-07-20 09:03:39 +02:00 committed by GitHub
parent 0017f236a7
commit b1c81b696f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 111 additions and 89 deletions

View File

@ -24,7 +24,7 @@ public class AppTopItems : ViewComponent
public async Task<IViewComponentResult> InvokeAsync(string appId, string appType)
{
var type = _appService.GetAppType(appType);
if (type is not IHasItemStatsAppType salesAppType || type is not AppBaseType appBaseType)
if (type is not (IHasItemStatsAppType and AppBaseType appBaseType))
return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty));
var vm = new AppTopItemsViewModel

View File

@ -1,6 +1,7 @@
@model BTCPayServer.Components.TruncateCenter.TruncateCenterViewModel
@{
var classes = string.IsNullOrEmpty(Model.Classes) ? string.Empty : Model.Classes.Trim();
var isTruncated = !string.IsNullOrEmpty(Model.Start) && !string.IsNullOrEmpty(Model.End);
@if (Model.Copy) classes += " truncate-center--copy";
@if (Model.Elastic) classes += " truncate-center--elastic";
}
@ -15,9 +16,12 @@
}
else
{
<span class="truncate-center-truncated" @(!string.IsNullOrEmpty(Model.Start) ? $"data-bs-toggle=tooltip title={Model.Text}" : "")>
<span class="truncate-center-start">@(Model.Elastic ? Model.Text : $"{Model.Start}…")</span>
<span class="truncate-center-end">@Model.End</span>
<span class="truncate-center-truncated" @(isTruncated ? $"data-bs-toggle=tooltip title={Model.Text}" : "")>
<span class="truncate-center-start">@(Model.Elastic || !isTruncated ? Model.Text : $"{Model.Start}…")</span>
@if (isTruncated)
{
<span class="truncate-center-end">@Model.End</span>
}
</span>
<span class="truncate-center-text">@Model.Text</span>
}

View File

@ -16,6 +16,7 @@ using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.HostedServices;
using BTCPayServer.Models;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Models.PaymentRequestViewModels;
using BTCPayServer.Payments;
@ -1084,22 +1085,22 @@ namespace BTCPayServer.Controllers
}
model.Search = fs;
model.SearchText = fs.TextSearch;
InvoiceQuery invoiceQuery = GetInvoiceQuery(fs, timezoneOffset);
var apps = await _appService.GetAllApps(GetUserId(), false, storeId);
InvoiceQuery invoiceQuery = GetInvoiceQuery(fs, apps, timezoneOffset);
invoiceQuery.StoreId = storeIds.ToArray();
invoiceQuery.Take = model.Count;
invoiceQuery.Skip = model.Skip;
invoiceQuery.IncludeRefunds = true;
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
// Apps
var apps = await _appService.GetAllApps(GetUserId(), false, storeId);
model.Apps = apps.Select(a => new InvoiceAppModel
{
Id = a.Id,
AppName = a.AppName,
AppType = a.AppType,
AppOrderId = AppService.GetAppOrderId(a.AppType, a.Id)
AppType = a.AppType
}).ToList();
foreach (var invoice in list)
@ -1124,11 +1125,21 @@ namespace BTCPayServer.Controllers
return View(model);
}
private InvoiceQuery GetInvoiceQuery(SearchString fs, int timezoneOffset = 0)
private InvoiceQuery GetInvoiceQuery(SearchString fs, ListAppsViewModel.ListAppViewModel[] apps, int timezoneOffset = 0)
{
var textSearch = fs.TextSearch;
if (fs.GetFilterArray("appid") is { } appIds)
{
var appsById = apps.ToDictionary(a => a.Id);
var searchTexts = appIds.Select(a => appsById.TryGet(a)).Where(a => a != null)
.Select(a => AppService.GetAppSearchTerm(a.AppType, a.Id))
.ToList();
searchTexts.Add(fs.TextSearch);
textSearch = string.Join(' ', searchTexts.Where(t => !string.IsNullOrEmpty(t)).ToList());
}
return new InvoiceQuery
{
TextSearch = fs.TextSearch,
TextSearch = textSearch,
UserId = GetUserId(),
Unusual = fs.GetFilterBool("unusual"),
IncludeArchived = fs.GetFilterBool("includearchived") ?? false,
@ -1160,7 +1171,8 @@ namespace BTCPayServer.Controllers
storeIds.Add(i);
}
InvoiceQuery invoiceQuery = GetInvoiceQuery(fs, timezoneOffset);
var apps = await _appService.GetAllApps(GetUserId(), false, storeId);
InvoiceQuery invoiceQuery = GetInvoiceQuery(fs, apps, timezoneOffset);
invoiceQuery.StoreId = storeIds.ToArray();
invoiceQuery.Skip = 0;
invoiceQuery.Take = int.MaxValue;

View File

@ -297,7 +297,6 @@ namespace BTCPayServer
var createInvoice = new CreateInvoiceRequest()
{
Amount = item?.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup? null: item?.Price,
Currency = currencyCode,
Checkout = new InvoiceDataBase.CheckoutOptions()
{
@ -307,11 +306,11 @@ namespace BTCPayServer
HttpContext.Request.GetAbsoluteUri($"/apps/{app.Id}/pos"),
_ => null
}
}
},
AdditionalSearchTerms = new[] { AppService.GetAppSearchTerm(app) }
};
var invoiceMetadata = new InvoiceMetadata();
invoiceMetadata.OrderId = AppService.GetAppOrderId(app);
var invoiceMetadata = new InvoiceMetadata { OrderId = AppService.GetRandomOrderId() };
if (item != null)
{
invoiceMetadata.ItemCode = item.Id;
@ -319,7 +318,6 @@ namespace BTCPayServer
}
createInvoice.Metadata = invoiceMetadata.ToJObject();
return await GetLNURLRequest(
cryptoCode,
store,

View File

@ -41,6 +41,5 @@ namespace BTCPayServer.Models.InvoicingModels
public string Id { get; set; }
public string AppName { get; set; }
public string AppType { get; set; }
public string AppOrderId { get; set; }
}
}

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Filters;
@ -13,6 +14,7 @@ using BTCPayServer.Models;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
@ -21,6 +23,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NBitpayClient;
using NicolasDorier.RateLimits;
using CrowdfundResetEvery = BTCPayServer.Services.Apps.CrowdfundResetEvery;
namespace BTCPayServer.Plugins.Crowdfund.Controllers
{
@ -175,33 +178,41 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
{
var appPath = await _appService.ViewLink(app);
var appUrl = HttpContext.Request.GetAbsoluteUri(appPath);
var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
var invoice = await _invoiceController.CreateInvoiceCoreRaw(new CreateInvoiceRequest()
{
OrderId = AppService.GetAppOrderId(app),
Amount = price,
Currency = settings.TargetCurrency,
ItemCode = request.ChoiceKey ?? string.Empty,
ItemDesc = title,
BuyerEmail = request.Email,
Price = price,
NotificationURL = settings.NotificationUrl,
FullNotifications = true,
ExtendedNotifications = true,
SupportedTransactionCurrencies = paymentMethods,
RedirectURL = request.RedirectUrl ?? appUrl,
Metadata = new InvoiceMetadata()
{
OrderId = AppService.GetRandomOrderId(),
ItemCode = request.ChoiceKey ?? string.Empty,
ItemDesc = title,
BuyerEmail = request.Email
}.ToJObject(),
Checkout = new InvoiceDataBase.CheckoutOptions()
{
RedirectURL = request.RedirectUrl ?? appUrl,
PaymentMethods = paymentMethods?.Where(p => p.Value.Enabled)
.Select(p => p.Key).ToArray()
},
AdditionalSearchTerms = new[] { AppService.GetAppSearchTerm(app) }
}, store, HttpContext.Request.GetAbsoluteRoot(),
new List<string> { AppService.GetAppInternalTag(appId) },
cancellationToken, entity =>
{
entity.NotificationURLTemplate = settings.NotificationUrl;
entity.FullNotifications = true;
entity.ExtendedNotifications = true;
entity.Metadata.OrderUrl = appUrl;
});
if (request.RedirectToCheckout)
{
return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice",
new { invoiceId = invoice.Data.Id });
new { invoiceId = invoice.Id });
}
return Ok(invoice.Data.Id);
return Ok(invoice.Id);
}
catch (BitpayHttpException e)
{
@ -248,7 +259,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
IsRecurring = resetEvery != nameof(CrowdfundResetEvery.Never),
UseAllStoreInvoices = app.TagAllInvoices,
AppId = appId,
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"orderid:{AppService.GetAppOrderId(app)}",
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"appid:{app.Id}",
DisplayPerksRanking = settings.DisplayPerksRanking,
DisplayPerksValue = settings.DisplayPerksValue,
SortPerksByPopularity = settings.SortPerksByPopularity,

View File

@ -12,6 +12,7 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Filters;
@ -35,6 +36,7 @@ using NBitcoin.DataEncoders;
using NBitpayClient;
using Newtonsoft.Json.Linq;
using NicolasDorier.RateLimits;
using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Plugins.PointOfSale.Controllers
{
@ -304,30 +306,37 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
}
try
{
var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest
var invoice = await _invoiceController.CreateInvoiceCoreRaw(new CreateInvoiceRequest
{
ItemCode = choice?.Id,
ItemDesc = title,
Amount = price,
Currency = settings.Currency,
Price = price,
BuyerEmail = email,
OrderId = orderId ?? AppService.GetAppOrderId(app),
NotificationURL =
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl,
RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl
: !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl
: Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), "UIPointOfSale", new { appId, viewType })),
FullNotifications = true,
ExtendedNotifications = true,
RedirectAutomatically = settings.RedirectAutomatically,
SupportedTransactionCurrencies = paymentMethods,
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
? storeBlob.RequiresRefundEmail
: requiresRefundEmail == RequiresRefundEmail.On,
Metadata = new InvoiceMetadata()
{
ItemCode = choice?.Id,
ItemDesc = title,
BuyerEmail = email,
OrderId = orderId ?? AppService.GetRandomOrderId()
}.ToJObject(),
Checkout = new InvoiceDataBase.CheckoutOptions()
{
RedirectAutomatically = settings.RedirectAutomatically,
RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl
: !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl
: Request.GetAbsoluteUri(Url.Action(nameof(ViewPointOfSale), "UIPointOfSale", new { appId, viewType })),
RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore
? storeBlob.RequiresRefundEmail
: requiresRefundEmail == RequiresRefundEmail.On,
PaymentMethods = paymentMethods?.Where(p => p.Value.Enabled).Select(p => p.Key).ToArray()
},
AdditionalSearchTerms = new [] { AppService.GetAppSearchTerm(app) }
}, store, HttpContext.Request.GetAbsoluteRoot(),
new List<string> { AppService.GetAppInternalTag(appId) },
cancellationToken, entity =>
{
entity.NotificationURLTemplate =
string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl;
entity.FullNotifications = true;
entity.ExtendedNotifications = true;
entity.Metadata.OrderUrl = Request.GetDisplayUrl();
entity.Metadata.PosData = jposData;
var receiptData = new JObject();
@ -380,9 +389,9 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
});
if (price is 0 && storeBlob.ReceiptOptions?.Enabled is true)
{
return RedirectToAction(nameof(UIInvoiceController.InvoiceReceipt), "UIInvoice", new { invoiceId = invoice.Data.Id });
return RedirectToAction(nameof(UIInvoiceController.InvoiceReceipt), "UIInvoice", new { invoiceId = invoice.Id });
}
return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", new { invoiceId = invoice.Data.Id });
return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", new { invoiceId = invoice.Id });
}
catch (BitpayHttpException e)
{
@ -537,7 +546,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
Description = settings.Description,
NotificationUrl = settings.NotificationUrl,
RedirectUrl = settings.RedirectUrl,
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"orderid:{AppService.GetAppOrderId(app)}",
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"appid:{app.Id}",
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : "",
FormId = settings.FormId
};

View File

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using ExchangeSharp;
namespace BTCPayServer
{

View File

@ -195,8 +195,8 @@ namespace BTCPayServer.Services.Apps
};
}
public static string GetAppOrderId(AppData app) => GetAppOrderId(app.AppType, app.Id);
public static string GetAppOrderId(string appType, string appId) =>
public static string GetAppSearchTerm(AppData app) => GetAppSearchTerm(app.AppType, app.Id);
public static string GetAppSearchTerm(string appType, string appId) =>
appType switch
{
CrowdfundAppType.AppType => $"crowdfund-app_{appId}",
@ -209,13 +209,18 @@ namespace BTCPayServer.Services.Apps
{
return invoice.GetInternalTags("APP#");
}
public static string GetRandomOrderId(int length = 16)
{
return Encoders.Base58.EncodeData(RandomUtils.GetBytes(length));
}
public static async Task<InvoiceEntity[]> GetInvoicesForApp(InvoiceRepository invoiceRepository, AppData appData, DateTimeOffset? startDate = null, string[]? status = null)
{
var invoices = await invoiceRepository.GetInvoices(new InvoiceQuery
{
StoreId = new[] { appData.StoreDataId },
OrderId = appData.TagAllInvoices ? null : new[] { GetAppOrderId(appData) },
TextSearch = appData.TagAllInvoices ? null : GetAppSearchTerm(appData),
Status = status ?? new[]{
InvoiceState.ToString(InvoiceStatusLegacy.New),
InvoiceState.ToString(InvoiceStatusLegacy.Paid),

View File

@ -180,7 +180,7 @@ namespace BTCPayServer.Services.Invoices
var textSearch = new HashSet<string>();
using (var context = _applicationDbContextFactory.CreateContext())
{
var invoiceData = new Data.InvoiceData()
var invoiceData = new InvoiceData
{
StoreDataId = invoice.StoreId,
Id = invoice.Id,
@ -529,7 +529,7 @@ namespace BTCPayServer.Services.Invoices
{
var invoiceIdSet = invoiceIds.ToHashSet();
using var context = _applicationDbContextFactory.CreateContext();
IQueryable<Data.InvoiceData> query =
IQueryable<InvoiceData> query =
context
.Invoices
.Include(o => o.Payments)
@ -540,7 +540,7 @@ namespace BTCPayServer.Services.Invoices
private async Task<InvoiceData> GetInvoiceRaw(string id, ApplicationDbContext dbContext, bool includeAddressData = false)
{
IQueryable<Data.InvoiceData> query =
IQueryable<InvoiceData> query =
dbContext
.Invoices
.Include(o => o.Payments);
@ -549,13 +549,10 @@ namespace BTCPayServer.Services.Invoices
query = query.Where(i => i.Id == id);
var invoice = (await query.ToListAsync()).FirstOrDefault();
if (invoice == null)
return null;
return invoice;
}
public InvoiceEntity ToEntity(Data.InvoiceData invoice)
public InvoiceEntity ToEntity(InvoiceData invoice)
{
var entity = invoice.GetBlob(_btcPayNetworkProvider);
PaymentMethodDictionary paymentMethods = null;
@ -606,9 +603,9 @@ namespace BTCPayServer.Services.Invoices
return entity;
}
private IQueryable<Data.InvoiceData> GetInvoiceQuery(ApplicationDbContext context, InvoiceQuery queryObject)
private IQueryable<InvoiceData> GetInvoiceQuery(ApplicationDbContext context, InvoiceQuery queryObject)
{
IQueryable<Data.InvoiceData> query = queryObject.UserId is null
IQueryable<InvoiceData> query = queryObject.UserId is null
? context.Invoices
: context.UserStore
.Where(u => u.ApplicationUserId == queryObject.UserId)
@ -619,7 +616,7 @@ namespace BTCPayServer.Services.Invoices
query = query.Where(i => !i.Archived);
}
if (queryObject.InvoiceId != null && queryObject.InvoiceId.Length > 0)
if (queryObject.InvoiceId is { Length: > 0 })
{
if (queryObject.InvoiceId.Length > 1)
{
@ -633,7 +630,7 @@ namespace BTCPayServer.Services.Invoices
}
}
if (queryObject.StoreId != null && queryObject.StoreId.Length > 0)
if (queryObject.StoreId is { Length: > 0 })
{
if (queryObject.StoreId.Length > 1)
{
@ -663,18 +660,18 @@ namespace BTCPayServer.Services.Invoices
if (queryObject.EndDate != null)
query = query.Where(i => i.Created <= queryObject.EndDate.Value);
if (queryObject.OrderId != null && queryObject.OrderId.Length > 0)
if (queryObject.OrderId is { Length: > 0 })
{
var statusSet = queryObject.OrderId.ToHashSet().ToArray();
query = query.Where(i => statusSet.Contains(i.OrderId));
}
if (queryObject.ItemCode != null && queryObject.ItemCode.Length > 0)
if (queryObject.ItemCode is { Length: > 0 })
{
var statusSet = queryObject.ItemCode.ToHashSet().ToArray();
query = query.Where(i => statusSet.Contains(i.ItemCode));
}
if (queryObject.Status != null && queryObject.Status.Length > 0)
if (queryObject.Status is { Length: > 0 })
{
var statusSet = queryObject.Status.ToHashSet();
// We make sure here that the old filters still work
@ -709,7 +706,7 @@ namespace BTCPayServer.Services.Invoices
query = query.Where(i => unused == (i.Status == "invalid" || !string.IsNullOrEmpty(i.ExceptionStatus)));
}
if (queryObject.ExceptionStatus != null && queryObject.ExceptionStatus.Length > 0)
if (queryObject.ExceptionStatus is { Length: > 0 })
{
var exceptionStatusSet = queryObject.ExceptionStatus.Select(s => NormalizeExceptionStatus(s)).ToHashSet().ToArray();
query = query.Where(i => exceptionStatusSet.Contains(i.ExceptionStatus));
@ -727,7 +724,7 @@ namespace BTCPayServer.Services.Invoices
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
{
using var context = _applicationDbContextFactory.CreateContext();
await using var context = _applicationDbContextFactory.CreateContext();
var query = GetInvoiceQuery(context, queryObject);
query = query.Include(o => o.Payments);
if (queryObject.IncludeAddresses)
@ -775,7 +772,6 @@ namespace BTCPayServer.Services.Invoices
return network == null ? JsonConvert.SerializeObject(data, DefaultSerializerSettings) : network.ToString(data);
}
public InvoiceStatistics GetContributionsByPaymentMethodId(string currency, InvoiceEntity[] invoices, bool softcap)
{
var contributions = invoices
@ -807,7 +803,6 @@ namespace BTCPayServer.Services.Invoices
p.Status == InvoiceStatusLegacy.Invalid)
return new[] { contribution };
// Else, we just sum the payments
return payments
.Select(pay =>

View File

@ -8,7 +8,7 @@
ViewData.SetActivePage(InvoiceNavPages.Index, "Invoices");
var statusFilterCount = CountArrayFilter("status") + CountArrayFilter("exceptionstatus") + (HasBooleanFilter("includearchived") ? 1 : 0) + (HasBooleanFilter("unusual") ? 1 : 0);
var hasDateFilter = HasArrayFilter("startdate") || HasArrayFilter("enddate");
var appFilterCount = Model.Apps.Count(app => HasArrayFilter("orderid", app.AppOrderId));
var appFilterCount = Model.Apps.Count(app => HasArrayFilter("appid", app.Id));
}
@functions
@ -140,7 +140,7 @@
})
.on("click", ".invoice-row", function (e) {
const $invoiceRow = $(e.currentTarget);
if (!$(e.target).is("a,.badge,.selector")) {
if ($(e.target).is("td")) {
$invoiceRow.find(".selector").trigger("click");
}
});
@ -279,7 +279,7 @@
<div class="dropdown-menu" aria-labelledby="AppOptionsToggle">
@foreach (var app in Model.Apps)
{
<a asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="@Model.Search.Toggle("orderid", app.AppOrderId)" class="dropdown-item @(HasArrayFilter("orderid", app.AppOrderId) ? "custom-active" : "")">@app.AppName</a>
<a asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="@Model.Search.Toggle("appid", app.Id)" class="dropdown-item @(HasArrayFilter("appid", app.Id) ? "custom-active" : "")">@app.AppName</a>
}
</div>
</div>
@ -379,16 +379,7 @@
</td>
<td>@invoice.Date.ToBrowserDate()</td>
<td>
<div class="text-truncate" style="max-width:120px;" data-bs-toggle="tooltip" title="@invoice.OrderId">
@if (invoice.RedirectUrl != string.Empty)
{
<a href="@invoice.RedirectUrl" rel="noreferrer noopener">@invoice.OrderId</a>
}
else
{
@invoice.OrderId
}
</div>
<vc:truncate-center text="@invoice.OrderId" link="@invoice.RedirectUrl" classes="truncate-center-id" />
</td>
<td class="text-break">@invoice.InvoiceId</td>
<td>