mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 09:54:30 +01:00
parent
2d68d0da63
commit
8fa65408ed
@ -12,6 +12,7 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
public string StoreDataId { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
||||
public StoreData StoreData { get; set; }
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20200508090807_AddArchivedToPaymentRequests")]
|
||||
public partial class AddArchivedToPaymentRequests : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Archived",
|
||||
table: "PaymentRequests",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (this.SupportDropColumn(ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Archived",
|
||||
table: "PaymentRequests");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -348,6 +348,9 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Archived")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("Blob")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
|
@ -70,14 +70,15 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> GetPaymentRequests(int skip = 0, int count = 50)
|
||||
public async Task<IActionResult> GetPaymentRequests(int skip = 0, int count = 50, bool includeArchived = false)
|
||||
{
|
||||
var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery()
|
||||
{
|
||||
UserId = GetUserId(), Skip = skip, Count = count
|
||||
UserId = GetUserId(), Skip = skip, Count = count, IncludeArchived = includeArchived
|
||||
});
|
||||
return View(new ListPaymentRequestsViewModel()
|
||||
{
|
||||
IncludeArchived = includeArchived,
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
Total = result.Total,
|
||||
@ -102,16 +103,14 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Html = $"Error: You need to create at least one store. <a href='{Url.Action("CreateStore", "UserStores")}' class='alert-link'>Create store</a>",
|
||||
Html =
|
||||
$"Error: You need to create at least one store. <a href='{Url.Action("CreateStore", "UserStores")}' class='alert-link'>Create store</a>",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction("GetPaymentRequests");
|
||||
}
|
||||
|
||||
return View(new UpdatePaymentRequestViewModel(data)
|
||||
{
|
||||
Stores = stores
|
||||
});
|
||||
return View(nameof(EditPaymentRequest), new UpdatePaymentRequestViewModel(data) {Stores = stores});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@ -128,13 +127,18 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if ( data?.Archived is true && viewModel?.Archived is true)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "You cannot edit an archived payment request.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
viewModel.Stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()),
|
||||
nameof(StoreData.Id),
|
||||
nameof(StoreData.StoreName), data?.StoreDataId);
|
||||
|
||||
return View(viewModel);
|
||||
return View(nameof(EditPaymentRequest),viewModel);
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
@ -143,6 +147,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
data.StoreDataId = viewModel.StoreId;
|
||||
data.Archived = viewModel.Archived;
|
||||
var blob = data.GetBlob();
|
||||
|
||||
blob.Title = viewModel.Title;
|
||||
@ -160,53 +165,12 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
data.Created = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data);
|
||||
_EventAggregator.Publish(new PaymentRequestUpdated()
|
||||
{
|
||||
Data = data,
|
||||
PaymentRequestId = data.Id,
|
||||
});
|
||||
_EventAggregator.Publish(new PaymentRequestUpdated() {Data = data, PaymentRequestId = data.Id,});
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Saved";
|
||||
return RedirectToAction("EditPaymentRequest", new {id = data.Id});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{id}/remove")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> RemovePaymentRequestPrompt(string id)
|
||||
{
|
||||
var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId());
|
||||
if (data == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var blob = data.GetBlob();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = $"Remove Payment Request",
|
||||
Description = $"Are you sure you want to remove access to the payment request '{blob.Title}' ?",
|
||||
Action = "Delete"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{id}/remove")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> RemovePaymentRequest(string id)
|
||||
{
|
||||
var result = await _PaymentRequestRepository.RemovePaymentRequest(id, GetUserId());
|
||||
if (result)
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Payment request successfully removed";
|
||||
return RedirectToAction("GetPaymentRequests");
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Payment request could not be removed. Any request that has generated invoices cannot be removed.";
|
||||
return RedirectToAction("GetPaymentRequests");
|
||||
}
|
||||
return RedirectToAction(nameof(EditPaymentRequest), new {id = data.Id});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -219,6 +183,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
result.HubPath = PaymentRequestHub.GetHubPath(this.Request);
|
||||
return View(result);
|
||||
}
|
||||
@ -233,11 +198,23 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return BadRequest("Please provide an amount greater than 0");
|
||||
}
|
||||
|
||||
var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId());
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (result.Archived)
|
||||
{
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
return RedirectToAction("ViewPaymentRequest", new {Id = id});
|
||||
}
|
||||
|
||||
return BadRequest("Payment Request cannot be paid as it has been archived");
|
||||
}
|
||||
|
||||
result.HubPath = PaymentRequestHub.GetHubPath(this.Request);
|
||||
if (result.AmountDue <= 0)
|
||||
{
|
||||
@ -259,10 +236,7 @@ namespace BTCPayServer.Controllers
|
||||
return BadRequest("Payment Request has expired");
|
||||
}
|
||||
|
||||
var statusesAllowedToDisplay = new List<InvoiceStatus>()
|
||||
{
|
||||
InvoiceStatus.New
|
||||
};
|
||||
var statusesAllowedToDisplay = new List<InvoiceStatus>() {InvoiceStatus.New};
|
||||
var validInvoice = result.Invoices.FirstOrDefault(invoice =>
|
||||
Enum.TryParse<InvoiceStatus>(invoice.Status, true, out var status) &&
|
||||
statusesAllowedToDisplay.Contains(status));
|
||||
@ -283,7 +257,7 @@ namespace BTCPayServer.Controllers
|
||||
amount = result.AmountDue;
|
||||
|
||||
|
||||
var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null);
|
||||
var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null, cancellationToken);
|
||||
var blob = pr.GetBlob();
|
||||
var store = pr.StoreData;
|
||||
try
|
||||
@ -326,7 +300,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var invoice = result.Invoices.SingleOrDefault(requestInvoice =>
|
||||
requestInvoice.Status.Equals(InvoiceState.ToString(InvoiceStatus.New),StringComparison.InvariantCulture) && !requestInvoice.Payments.Any());
|
||||
requestInvoice.Status.Equals(InvoiceState.ToString(InvoiceStatus.New),
|
||||
StringComparison.InvariantCulture) && !requestInvoice.Payments.Any());
|
||||
|
||||
if (invoice == null)
|
||||
{
|
||||
@ -334,15 +309,13 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoice.Id);
|
||||
_EventAggregator.Publish(new InvoiceEvent(await _InvoiceRepository.GetInvoice(invoice.Id), 1008, InvoiceEvent.MarkedInvalid));
|
||||
_EventAggregator.Publish(new InvoiceEvent(await _InvoiceRepository.GetInvoice(invoice.Id), 1008,
|
||||
InvoiceEvent.MarkedInvalid));
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Payment cancelled";
|
||||
return RedirectToAction(nameof(ViewPaymentRequest), new
|
||||
{
|
||||
Id = id
|
||||
});
|
||||
return RedirectToAction(nameof(ViewPaymentRequest), new {Id = id});
|
||||
}
|
||||
|
||||
return Ok("Payment cancelled");
|
||||
@ -362,10 +335,27 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var model = (UpdatePaymentRequestViewModel)viewResult.Model;
|
||||
model.Id = null;
|
||||
model.Archived = false;
|
||||
model.Title = $"Clone of {model.Title}";
|
||||
|
||||
return View("EditPaymentRequest", model);
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("{id}/archive")]
|
||||
public async Task<IActionResult> TogglePaymentRequestArchival(string id)
|
||||
{
|
||||
var result = await EditPaymentRequest(id);
|
||||
if (result is ViewResult viewResult)
|
||||
{
|
||||
var model = (UpdatePaymentRequestViewModel)viewResult.Model;
|
||||
model.Archived = !model.Archived;
|
||||
result = await EditPaymentRequest(id, model);
|
||||
TempData[WellKnownTempData.SuccessMessage] = model.Archived
|
||||
? "The payment request has been archived and will no longer appear in the payment request list by default again."
|
||||
: "The payment request has been unarchived and will appear in the payment request list by default.";
|
||||
return result;
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
|
@ -16,6 +16,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
public List<ViewPaymentRequestViewModel> Items { get; set; }
|
||||
|
||||
public int Total { get; set; }
|
||||
public bool IncludeArchived { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatePaymentRequestViewModel
|
||||
@ -33,7 +34,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
|
||||
Id = data.Id;
|
||||
StoreId = data.StoreDataId;
|
||||
|
||||
Archived = data.Archived;
|
||||
var blob = data.GetBlob();
|
||||
Title = blob.Title;
|
||||
Amount = blob.Amount;
|
||||
@ -46,6 +47,8 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts;
|
||||
}
|
||||
|
||||
public bool Archived { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
[Required] public string StoreId { get; set; }
|
||||
|
||||
@ -81,6 +84,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
{
|
||||
Id = data.Id;
|
||||
var blob = data.GetBlob();
|
||||
Archived = data.Archived;
|
||||
Title = blob.Title;
|
||||
Amount = blob.Amount;
|
||||
Currency = blob.Currency;
|
||||
@ -110,28 +114,20 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
}
|
||||
|
||||
public bool AllowCustomPaymentAmounts { get; set; }
|
||||
|
||||
|
||||
public string Email { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
public bool IsPending { get; set; }
|
||||
|
||||
public decimal AmountCollected { get; set; }
|
||||
public decimal AmountDue { get; set; }
|
||||
public string AmountDueFormatted { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Currency { get; set; }
|
||||
|
||||
public DateTime? ExpiryDate { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public string CustomCSSLink { get; set; }
|
||||
|
||||
public List<PaymentRequestInvoice> Invoices { get; set; } = new List<PaymentRequestInvoice>();
|
||||
public DateTime LastUpdated { get; set; }
|
||||
public CurrencyData CurrencyData { get; set; }
|
||||
@ -140,6 +136,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
public bool AnyPendingInvoice { get; set; }
|
||||
public bool PendingInvoiceHasPayments { get; set; }
|
||||
public string HubPath { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
||||
public class PaymentRequestInvoice
|
||||
{
|
||||
|
@ -87,6 +87,7 @@ namespace BTCPayServer.PaymentRequest
|
||||
|
||||
return new ViewPaymentRequestViewModel(pr)
|
||||
{
|
||||
Archived = pr.Archived,
|
||||
AmountFormatted = _currencies.FormatCurrency(blob.Amount, blob.Currency),
|
||||
AmountCollected = paymentStats.TotalCurrency,
|
||||
AmountCollectedFormatted = _currencies.FormatCurrency(paymentStats.TotalCurrency, blob.Currency),
|
||||
|
@ -94,6 +94,11 @@ namespace BTCPayServer.Services.PaymentRequests
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var queryable = context.PaymentRequests.Include(data => data.StoreData).AsQueryable();
|
||||
|
||||
if (!query.IncludeArchived)
|
||||
{
|
||||
queryable = queryable.Where(data => !data.Archived);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(query.StoreId))
|
||||
{
|
||||
queryable = queryable.Where(data =>
|
||||
@ -129,25 +134,6 @@ namespace BTCPayServer.Services.PaymentRequests
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> RemovePaymentRequest(string id, string userId)
|
||||
{
|
||||
using (var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var canDelete = !(await GetInvoicesForPaymentRequest(id)).Any();
|
||||
if (!canDelete) return false;
|
||||
var pr = await FindPaymentRequest(id, userId);
|
||||
if (pr == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context.PaymentRequests.Remove(pr);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<InvoiceEntity[]> GetInvoicesForPaymentRequest(string paymentRequestId,
|
||||
InvoiceQuery invoiceQuery = null)
|
||||
{
|
||||
@ -195,7 +181,7 @@ namespace BTCPayServer.Services.PaymentRequests
|
||||
public class PaymentRequestQuery
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
|
||||
public bool IncludeArchived { get; set; } = true;
|
||||
public PaymentRequestData.PaymentRequestStatus[] Status{ get; set; }
|
||||
public string UserId { get; set; }
|
||||
public int? Skip { get; set; }
|
||||
|
@ -107,7 +107,18 @@
|
||||
asp-controller="Invoice"
|
||||
asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(Model.Id)}")">Invoices</a>
|
||||
<a class="btn btn-secondary" asp-route-id="@this.Context.GetRouteValue("id")" asp-action="ClonePaymentRequest" id="@Model.Id">Clone</a>
|
||||
@if (!Model.Archived)
|
||||
{
|
||||
<a class="btn btn-secondary" data-toggle="tooltip" title="Archive this payment request so that it does not appear in the payment request list by default" asp-route-id="@this.Context.GetRouteValue("id")" asp-controller="PaymentRequest" asp-action="TogglePaymentRequestArchival">Archive</a>
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" data-toggle="tooltip" title="Unarchive this payment request" asp-route-id="@this.Context.GetRouteValue("id")" asp-controller="PaymentRequest" asp-action="TogglePaymentRequestArchival" >Unarchive</a>
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
<a class="btn btn-secondary" target="_blank" asp-action="GetPaymentRequests">Back to list</a>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -23,7 +23,9 @@
|
||||
<div class="row button-row">
|
||||
<div class="col-lg-12">
|
||||
<a asp-action="EditPaymentRequest" class="btn btn-primary" role="button" id="CreatePaymentRequest"><span class="fa fa-plus"></span> Create a new payment request</a>
|
||||
<a href="https://docs.btcpayserver.org/features/paymentrequests" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
||||
<a href="https://docs.btcpayserver.org/features/paymentrequests" target="_blank">
|
||||
<span class="fa fa-question-circle-o" title="More information..."></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -57,13 +59,16 @@
|
||||
<span> - </span>
|
||||
<a target="_blank" asp-action="ClonePaymentRequest" asp-route-id="@item.Id">Clone</a>
|
||||
<span> - </span>
|
||||
<a asp-action="RemovePaymentRequestPrompt" asp-route-id="@item.Id">Remove</a>
|
||||
<a asp-action="TogglePaymentRequestArchival" asp-route-id="@item.Id">@(item.Archived ? "Unarchive" : "Archive")</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="d-flex ">
|
||||
|
||||
|
||||
<nav aria-label="...">
|
||||
<ul class="pagination">
|
||||
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
|
||||
@ -71,7 +76,9 @@
|
||||
{
|
||||
skip = Math.Max(0, Model.Skip - Model.Count),
|
||||
count = Model.Count,
|
||||
})">Previous</a>
|
||||
})">
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">@(Model.Skip + 1) to @(Model.Skip + Model.Count) of @Model.Total</span>
|
||||
@ -81,10 +88,30 @@
|
||||
{
|
||||
skip = Model.Skip + Model.Count,
|
||||
count = Model.Count,
|
||||
})">Next</a>
|
||||
})">
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
<a class="ml-2 mt-1" href="@Url.Action("GetPaymentRequests", new
|
||||
{
|
||||
skip = Model.Skip,
|
||||
count = Model.Count,
|
||||
includeArchived = !Model.IncludeArchived
|
||||
})">
|
||||
@if (Model.IncludeArchived)
|
||||
{
|
||||
<span> Hide archived payment requests. </span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span> Show archived payment requests.</span>
|
||||
}
|
||||
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -102,7 +102,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (Model.IsPending)
|
||||
@if (Model.IsPending && !Model.Archived)
|
||||
{
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">
|
||||
@ -147,6 +147,13 @@
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}else if (Model.Archived)
|
||||
{
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">
|
||||
<button type="button" class="btn btn-secondary" disabled>Archived</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -143,7 +143,12 @@ else
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="!ended && (srvModel.amountDue) > 0" class="d-print-none">
|
||||
<tr v-if="srvModel.archived">
|
||||
<td colspan="4" class="text-center">
|
||||
<button type="button" class="btn btn-secondary" disabled>Archived</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="!ended && (srvModel.amountDue) > 0" class="d-print-none">
|
||||
<td colspan="4" class="text-center">
|
||||
|
||||
<template v-if="srvModel.allowCustomPaymentAmounts && !srvModel.anyPendingInvoice">
|
||||
|
Loading…
Reference in New Issue
Block a user