1
0
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:
Kyle Spearrin 2019-02-25 12:43:20 -05:00
parent 7ee8c0a240
commit 3b8552b2fa
14 changed files with 302 additions and 32 deletions

View File

@ -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>

View File

@ -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 });
}
}
}
}

View 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; }
}
}

View 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
};
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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");

View File

@ -32,3 +32,7 @@ h3 {
.validation-summary-valid {
display: none;
}
.alert.validation-summary-errors > ul {
margin-bottom: 0;
}

View File

@ -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">

View File

@ -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>

View File

@ -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">

View File

@ -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>
}

View 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>

View File

@ -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">

View File

@ -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; }