mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
billing info and tx management tools
This commit is contained in:
parent
7ee8c0a240
commit
3b8552b2fa
@ -20,6 +20,9 @@
|
||||
<Content Update="Views\Shared\_BillingInformation.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Tools\CreateUpdateTransaction.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -14,10 +15,14 @@ namespace Bit.Admin.Controllers
|
||||
public class ToolsController : Controller
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ITransactionRepository _transactionRepository;
|
||||
|
||||
public ToolsController(GlobalSettings globalSettings)
|
||||
public ToolsController(
|
||||
GlobalSettings globalSettings,
|
||||
ITransactionRepository transactionRepository)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_transactionRepository = transactionRepository;
|
||||
}
|
||||
|
||||
public IActionResult ChargeBraintree()
|
||||
@ -76,5 +81,61 @@ namespace Bit.Admin.Controllers
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
|
||||
public IActionResult CreateTransaction(Guid? organizationId = null, Guid? userId = null)
|
||||
{
|
||||
return View("CreateUpdateTransaction", new CreateUpdateTransactionModel
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
UserId = userId
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateTransaction(CreateUpdateTransactionModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View("CreateUpdateTransaction", model);
|
||||
}
|
||||
|
||||
await _transactionRepository.CreateAsync(model.ToTransaction());
|
||||
if(model.UserId.HasValue)
|
||||
{
|
||||
return RedirectToAction("Edit", "Users", new { id = model.UserId });
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToAction("Edit", "Organizations", new { id = model.OrganizationId });
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IActionResult> EditTransaction(Guid id)
|
||||
{
|
||||
var transaction = await _transactionRepository.GetByIdAsync(id);
|
||||
if(transaction == null)
|
||||
{
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
return View("CreateUpdateTransaction", new CreateUpdateTransactionModel(transaction));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> EditTransaction(Guid id, CreateUpdateTransactionModel model)
|
||||
{
|
||||
if(!ModelState.IsValid)
|
||||
{
|
||||
return View("CreateUpdateTransaction", model);
|
||||
}
|
||||
await _transactionRepository.ReplaceAsync(model.ToTransaction(id));
|
||||
if(model.UserId.HasValue)
|
||||
{
|
||||
return RedirectToAction("Edit", "Users", new { id = model.UserId });
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToAction("Edit", "Organizations", new { id = model.OrganizationId });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
src/Admin/Models/BillingInformationModel.cs
Normal file
12
src/Admin/Models/BillingInformationModel.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public class BillingInformationModel
|
||||
{
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public Guid? OrganizationId { get; set; }
|
||||
}
|
||||
}
|
80
src/Admin/Models/CreateUpdateTransactionModel.cs
Normal file
80
src/Admin/Models/CreateUpdateTransactionModel.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public class CreateUpdateTransactionModel : IValidatableObject
|
||||
{
|
||||
public CreateUpdateTransactionModel() { }
|
||||
|
||||
public CreateUpdateTransactionModel(Transaction transaction)
|
||||
{
|
||||
Edit = true;
|
||||
UserId = transaction.UserId;
|
||||
OrganizationId = transaction.OrganizationId;
|
||||
Amount = transaction.Amount;
|
||||
RefundedAmount = transaction.RefundedAmount;
|
||||
Refunded = transaction.Refunded.GetValueOrDefault();
|
||||
Details = transaction.Details;
|
||||
Date = transaction.CreationDate;
|
||||
PaymentMethod = transaction.PaymentMethodType;
|
||||
Gateway = transaction.Gateway;
|
||||
GatewayId = transaction.GatewayId;
|
||||
Type = transaction.Type;
|
||||
}
|
||||
|
||||
public bool Edit { get; set; }
|
||||
|
||||
[Display(Name = "User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Display(Name = "Organization Id")]
|
||||
public Guid? OrganizationId { get; set; }
|
||||
[Required]
|
||||
public decimal? Amount { get; set; }
|
||||
[Display(Name = "Refunded Amount")]
|
||||
public decimal? RefundedAmount { get; set; }
|
||||
public bool Refunded { get; set; }
|
||||
[Required]
|
||||
public string Details { get; set; }
|
||||
[Required]
|
||||
public DateTime? Date { get; set; }
|
||||
[Display(Name = "Payment Method")]
|
||||
public PaymentMethodType? PaymentMethod { get; set; }
|
||||
public GatewayType? Gateway { get; set; }
|
||||
[Display(Name = "Gateway Id")]
|
||||
public string GatewayId { get; set; }
|
||||
[Required]
|
||||
public TransactionType? Type { get; set; }
|
||||
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if((!UserId.HasValue && !OrganizationId.HasValue) || (UserId.HasValue && OrganizationId.HasValue))
|
||||
{
|
||||
yield return new ValidationResult("Must provide either User Id, or Organization Id.");
|
||||
}
|
||||
}
|
||||
|
||||
public Transaction ToTransaction(Guid? id = null)
|
||||
{
|
||||
return new Transaction
|
||||
{
|
||||
Id = id.GetValueOrDefault(),
|
||||
UserId = UserId,
|
||||
OrganizationId = OrganizationId,
|
||||
Amount = Amount.Value,
|
||||
RefundedAmount = RefundedAmount,
|
||||
Refunded = Refunded ? true : (bool?)null,
|
||||
Details = Details,
|
||||
CreationDate = Date.Value,
|
||||
PaymentMethodType = PaymentMethod,
|
||||
Gateway = Gateway,
|
||||
GatewayId = GatewayId,
|
||||
Type = Type.Value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Admin.Models
|
||||
{
|
||||
public class TransactionViewModel
|
||||
{
|
||||
public TransactionViewModel() { }
|
||||
|
||||
public TransactionViewModel(User user, IEnumerable<Cipher> ciphers)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ namespace Bit.Admin.Models
|
||||
LicenseKey = user.LicenseKey;
|
||||
PremiumExpirationDate = user.PremiumExpirationDate;
|
||||
}
|
||||
|
||||
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public string RandomLicenseKey => CoreHelpers.SecureRandomString(20);
|
||||
public string OneYearExpirationDate => DateTime.Now.AddYears(1).ToString("yyyy-MM-ddTHH:mm");
|
||||
|
@ -32,3 +32,7 @@ h3 {
|
||||
.validation-summary-valid {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.validation-summary-errors > ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -77,7 +77,8 @@
|
||||
<h2>Organization Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model)
|
||||
<h2>Billing Information</h2>
|
||||
@await Html.PartialAsync("_BillingInformation", Model.BillingInfo)
|
||||
@await Html.PartialAsync("_BillingInformation",
|
||||
new BillingInformationModel { BillingInfo = Model.BillingInfo, OrganizationId = Model.Organization.Id })
|
||||
<form method="post" id="edit-form">
|
||||
<h2>General</h2>
|
||||
<div class="row">
|
||||
|
@ -1,21 +1,26 @@
|
||||
@model Bit.Core.Models.Business.BillingInfo
|
||||
@model BillingInformationModel
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4 col-lg-3">Account @(Model.Balance <= 0 ? "Credit" : "Balance")</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Math.Abs(Model.Balance).ToString("C")</dd>
|
||||
<dt class="col-sm-4 col-lg-3">Account @(Model.BillingInfo.Balance <= 0 ? "Credit" : "Balance")</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Math.Abs(Model.BillingInfo.Balance).ToString("C")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Invoices</dt>
|
||||
<dd class="col-sm-8 col-lg-9">
|
||||
@if(Model.Invoices?.Any() ?? false)
|
||||
@if(Model.BillingInfo.Invoices?.Any() ?? false)
|
||||
{
|
||||
<table class="table">
|
||||
<tbody>
|
||||
@foreach(var invoice in Model.Invoices)
|
||||
@foreach(var invoice in Model.BillingInfo.Invoices)
|
||||
{
|
||||
<tr>
|
||||
<td>@invoice.Date</td>
|
||||
<td><a target="_blank" href="@invoice.Url" title="View Invoice">@invoice.Number</a></td>
|
||||
<td>@invoice.Amount.ToString("C")</td>
|
||||
<td>@(invoice.Paid ? "Paid" : "Unpaid")</td>
|
||||
<td>
|
||||
<a target="_blank" href="@invoice.PdfUrl" title="Download Invoice">
|
||||
<i class="fa fa-file-pdf-o"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@ -29,11 +34,11 @@
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Transactions</dt>
|
||||
<dd class="col-sm-8 col-lg-9">
|
||||
@if(Model.Transactions?.Any() ?? false)
|
||||
@if(Model.BillingInfo.Transactions?.Any() ?? false)
|
||||
{
|
||||
<table class="table">
|
||||
<tbody>
|
||||
@foreach(var transaction in Model.Transactions)
|
||||
@foreach(var transaction in Model.BillingInfo.Transactions)
|
||||
{
|
||||
<tr>
|
||||
<td>@transaction.CreatedDate</td>
|
||||
@ -41,6 +46,10 @@
|
||||
<td>@transaction.PaymentMethodType.ToString()</td>
|
||||
<td>@transaction.Details</td>
|
||||
<td>@transaction.Amount.ToString("C")</td>
|
||||
<td>
|
||||
<a title="Edit Transaction" asp-controller="Tools" asp-action="EditTransaction"
|
||||
asp-route-id="@transaction.Id"><i class="fa fa-edit"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@ -48,7 +57,11 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@: No transactions.
|
||||
<p>No transactions.</p>
|
||||
}
|
||||
<a asp-action="CreateTransaction" asp-controller="Tools" asp-route-organizationId="@Model.OrganizationId"
|
||||
asp-route-userId="@Model.UserId" class="btn btn-sm btn-outline-primary">
|
||||
<i class="fa fa-plus"></i> New Transaction
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
@ -49,6 +49,9 @@
|
||||
<a class="dropdown-item" asp-controller="Tools" asp-action="ChargeBraintree">
|
||||
Charge Braintree Customer
|
||||
</a>
|
||||
<a class="dropdown-item" asp-controller="Tools" asp-action="CreateTransaction">
|
||||
Create Transaction
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item" active-controller="Logs">
|
||||
|
@ -39,6 +39,6 @@ else
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mb-2" title="Search">Charge Customer</button>
|
||||
<button type="submit" class="btn btn-primary mb-2">Charge Customer</button>
|
||||
</form>
|
||||
}
|
||||
|
109
src/Admin/Views/Tools/CreateUpdateTransaction.cshtml
Normal file
109
src/Admin/Views/Tools/CreateUpdateTransaction.cshtml
Normal file
@ -0,0 +1,109 @@
|
||||
@model CreateUpdateTransactionModel
|
||||
@{
|
||||
var action = Model.Edit ? "Edit" : "Create";
|
||||
ViewData["Title"] = $"{action} Transaction";
|
||||
}
|
||||
|
||||
<h1>@action Transaction</h1>
|
||||
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="UserId"></label>
|
||||
<input type="text" class="form-control" asp-for="UserId">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="OrganizationId"></label>
|
||||
<input type="text" class="form-control" asp-for="OrganizationId">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="Date"></label>
|
||||
<input type="datetime-local" class="form-control" asp-for="Date" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<label asp-for="Type"></label>
|
||||
<select class="form-control" asp-for="Type" required
|
||||
asp-items="Html.GetEnumSelectList<Bit.Core.Enums.TransactionType>()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="Amount"></label>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">$</span>
|
||||
</div>
|
||||
<input type="number" min="0.01" max="1000000.00" step="0.01" class="form-control"
|
||||
asp-for="Amount" required placeholder="ex. 10.00">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="RefundedAmount"></label>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">$</span>
|
||||
</div>
|
||||
<input type="number" min="0.01" max="1000000.00" step="0.01" class="form-control"
|
||||
asp-for="RefundedAmount" placeholder="ex. 10.00">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" asp-for="Refunded">
|
||||
<label class="form-check-label" asp-for="Refunded"></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Details"></label>
|
||||
<input type="text" class="form-control" asp-for="Details" required>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<label asp-for="Gateway"></label>
|
||||
<select class="form-control" asp-for="Gateway"
|
||||
asp-items="Html.GetEnumSelectList<Bit.Core.Enums.GatewayType>()">
|
||||
<option value="">--</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="GatewayId"></label>
|
||||
<input type="text" class="form-control" asp-for="GatewayId">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<label asp-for="PaymentMethod"></label>
|
||||
<select class="form-control" asp-for="PaymentMethod"
|
||||
asp-items="Html.GetEnumSelectList<Bit.Core.Enums.PaymentMethodType>()">
|
||||
<option value="">--</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mb-2">@action Transaction</button>
|
||||
</form>
|
@ -59,7 +59,8 @@
|
||||
<h2>User Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model)
|
||||
<h2>Billing Information</h2>
|
||||
@await Html.PartialAsync("_BillingInformation", Model.BillingInfo)
|
||||
@await Html.PartialAsync("_BillingInformation",
|
||||
new BillingInformationModel { BillingInfo = Model.BillingInfo, UserId = Model.User.Id })
|
||||
<form method="post" id="edit-form">
|
||||
<h2>General</h2>
|
||||
<div class="row">
|
||||
|
@ -88,6 +88,7 @@ namespace Bit.Core.Models.Business
|
||||
{
|
||||
public BillingTransaction(Transaction transaction)
|
||||
{
|
||||
Id = transaction.Id;
|
||||
CreatedDate = transaction.CreationDate;
|
||||
Refunded = transaction.Refunded;
|
||||
Type = transaction.Type;
|
||||
@ -97,6 +98,7 @@ namespace Bit.Core.Models.Business
|
||||
RefundedAmount = transaction.RefundedAmount;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public bool? Refunded { get; set; }
|
||||
|
Loading…
Reference in New Issue
Block a user