Skip to content

Commit

Permalink
feat: allow admin to topup wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
kirinnee committed Jan 7, 2024
1 parent c12225d commit 8cf8e66
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 6 deletions.
54 changes: 54 additions & 0 deletions App/Modules/Admin/API/V1/AdminController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Net.Mime;
using App.Modules.Common;
using App.Modules.Wallets.API.V1;
using App.StartUp.Registry;
using App.StartUp.Services.Auth;
using App.Utility;
using Asp.Versioning;
using CSharp_Result;
using Domain.Admin;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace App.Modules.Admin.API.V1;

[ApiVersion(1.0)]
[ApiController]
[Consumes(MediaTypeNames.Application.Json)]
[Route("api/v{version:apiVersion}/[controller]")]
public class AdminController(
IAdminService service,
TransferReqValidator transferReqValidator,
IAuthHelper h
) : AtomiControllerBase(h)
{
[Authorize(Policy = AuthPolicies.OnlyAdmin), HttpPost("inflow/{userId}")]
public async Task<ActionResult<WalletPrincipalRes>> TransferIn(string userId, [FromBody] TransferReq req)
{
var x = await transferReqValidator
.ValidateAsyncResult(req, "Invalid TransferReq")
.ThenAwait(q => service.TransferIn(userId, q.Amount, q.Desc))
.Then(x => x.ToRes(), Errors.MapNone);
return this.ReturnResult(x);
}

[Authorize(Policy = AuthPolicies.OnlyAdmin), HttpPost("outflow/{userId}")]
public async Task<ActionResult<WalletPrincipalRes>> TransferOut(string userId, [FromBody] TransferReq req)
{
var x = await transferReqValidator
.ValidateAsyncResult(req, "Invalid TransferReq")
.ThenAwait(q => service.TransferOut(userId, q.Amount, q.Desc))
.Then(x => x.ToRes(), Errors.MapNone);
return this.ReturnResult(x);
}

[Authorize(Policy = AuthPolicies.OnlyAdmin), HttpPost("promo/{userId}")]
public async Task<ActionResult<WalletPrincipalRes>> PromotionalCredit(string userId, [FromBody] TransferReq req)
{
var x = await transferReqValidator
.ValidateAsyncResult(req, "Invalid TransferReq")
.ThenAwait(q => service.Promo(userId, q.Amount, q.Desc))
.Then(x => x.ToRes(), Errors.MapNone);
return this.ReturnResult(x);
}
}
6 changes: 6 additions & 0 deletions App/Modules/Admin/API/V1/AdminModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace App.Modules.Admin.API.V1;

public record TransferReq(
decimal Amount, string Desc
);

17 changes: 17 additions & 0 deletions App/Modules/Admin/API/V1/AdminValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using App.Utility;
using FluentValidation;

namespace App.Modules.Admin.API.V1;

public class TransferReqValidator : AbstractValidator<TransferReq>
{
public TransferReqValidator()
{
this.RuleFor(x => x.Amount)
.NotNull()
.Must(x => x > 0);
this.RuleFor(x => x.Desc)
.NotNull()
.TransactionDescriptionValid();
}
}
5 changes: 5 additions & 0 deletions App/Modules/DomainServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using App.Modules.Wallets.Data;
using App.StartUp.Services;
using Domain;
using Domain.Admin;
using Domain.Booking;
using Domain.Cost;
using Domain.Passenger;
Expand Down Expand Up @@ -96,6 +97,10 @@ public static IServiceCollection AddDomainServices(this IServiceCollection s)
s.AddScoped<ICostCalculator, SimpleCostCalculator>()
.AutoTrace<ICostCalculator>();

// Admin
s.AddScoped<IAdminService, AdminService>()
.AutoTrace<IAdminService>();

return s;
}
}
29 changes: 29 additions & 0 deletions App/Modules/Wallets/Data/WalletRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,35 @@ public async Task<Result<IEnumerable<WalletPrincipal>>> Search(WalletSearch sear
}
}

public async Task<Result<WalletPrincipal?>> Collect(Guid id, decimal amount)
{
try
{
logger.LogInformation("Collecting from wallet with Id '{id}' with {amount}", id, amount);
var wallet = await db
.Wallets
.Where(x => x.Id == id)
.FirstOrDefaultAsync();
if (wallet is null) return wallet?.ToPrincipal();

if (wallet.Usable < amount)
return new InsufficientBalance("Insufficient balance to collect",
wallet.UserId, wallet.Id, amount,
Accounts.Usable.Id)
.ToException();
wallet.Usable -= amount;

await db.SaveChangesAsync();
return wallet.ToPrincipal();
}
catch (Exception e)
{
logger
.LogError(e, "Collecting from wallet with Id: {id} with {amount}", id, amount);
throw;
}
}

public async Task<Result<WalletPrincipal?>> BookStart(Guid id, decimal amount)
{
try
Expand Down
8 changes: 7 additions & 1 deletion App/Utility/ValidationUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,13 @@ public static IRuleBuilderOptions<T, string> NameValid<T>(
.Length(1, 256)
.WithMessage("Name has to be between 1 to 256 characters");
}

public static IRuleBuilderOptions<T, string> TransactionDescriptionValid<T>(
this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder
.Length(2, 4096)
.WithMessage("Description has to be between 2 to 4096 characters");
}

public static IRuleBuilderOptions<T, string> DescriptionValid<T>(
this IRuleBuilder<T, string> ruleBuilder)
Expand Down
13 changes: 13 additions & 0 deletions Domain/Admin/IService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CSharp_Result;
using Domain.Wallet;

namespace Domain.Admin;

public interface IAdminService
{
Task<Result<WalletPrincipal>> TransferIn(string userId, decimal amount, string desc);

Task<Result<WalletPrincipal>> TransferOut(string userId, decimal amount, string desc);

Task<Result<WalletPrincipal>> Promo(string userId, decimal amount, string desc);
}
50 changes: 50 additions & 0 deletions Domain/Admin/Service.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using CSharp_Result;
using Domain.Transaction;
using Domain.Wallet;

namespace Domain.Admin;

public class AdminService(
IWalletRepository walletRepo,
ITransactionRepository transactionRepo,
ITransactionGenerator transactionGenerator,
ITransactionManager transaction
) : IAdminService
{

public Task<Result<WalletPrincipal>> TransferIn(string userId, decimal amount, string desc)
{
return transaction.Start(() =>
walletRepo.GetByUserId(userId)
.NullToError(userId)
.ThenAwait(w => walletRepo.Deposit(w.Principal.Id, amount))
.NullToError(userId)
.DoAwait(DoType.MapErrors, w => transactionRepo.Create(w.Id,
transactionGenerator.AdminInflow(amount, desc)))
);
}

public Task<Result<WalletPrincipal>> TransferOut(string userId, decimal amount, string desc)
{
return transaction.Start(() =>
walletRepo.GetByUserId(userId)
.NullToError(userId)
.ThenAwait(w => walletRepo.Collect(w.Principal.Id, amount))
.NullToError(userId)
.DoAwait(DoType.MapErrors, w => transactionRepo.Create(w.Id,
transactionGenerator.AdminOutflow(amount, desc)))
);
}

public Task<Result<WalletPrincipal>> Promo(string userId, decimal amount, string desc)
{
return transaction.Start(() =>
walletRepo.GetByUserId(userId)
.NullToError(userId)
.ThenAwait(w => walletRepo.Deposit(w.Principal.Id, amount))
.NullToError(userId)
.DoAwait(DoType.MapErrors, w => transactionRepo.Create(w.Id,
transactionGenerator.Promotional(amount, desc)))
);
}
}
4 changes: 0 additions & 4 deletions Domain/Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,4 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<Folder Include="Admin\" />
</ItemGroup>

</Project>
49 changes: 48 additions & 1 deletion Domain/Transaction/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ public interface ITransactionGenerator

public TransactionRecord CancelBooking(TransactionRecord create, BookingRecord booking);


public TransactionRecord TerminateBooking(TransactionRecord create, BookingRecord booking);

// Admin Flow
public TransactionRecord AdminInflow(decimal amount, string description);

public TransactionRecord AdminOutflow(decimal amount, string description);

public TransactionRecord Promotional(decimal amount, string description);
}

public class TransactionGenerator(IRefundCalculator calculator) : ITransactionGenerator
Expand Down Expand Up @@ -99,4 +105,45 @@ public TransactionRecord TerminateBooking(TransactionRecord create, BookingRecor
To = Accounts.Usable.DisplayName,
};
}

public TransactionRecord AdminInflow(decimal amount, string description)
{
return new TransactionRecord
{
Name = "BunnyBooker Admin Inflow",
Description =
$"The BunnyBooker Admin has transferred SGD ${amount} credits to your Usable account. " + description,
Type = TransactionType.Transfer,
Amount = amount,
From = Accounts.BunnyBooker.DisplayName,
To = Accounts.Usable.DisplayName,
};
}

public TransactionRecord AdminOutflow(decimal amount, string description)
{
return new TransactionRecord
{
Name = "BunnyBooker Admin Outflow",
Description =
$"The BunnyBooker Admin has transferred SGD ${amount} credits out of your Usable account. " + description,
Type = TransactionType.Transfer,
Amount = amount,
From = Accounts.Usable.DisplayName,
To = Accounts.BunnyBooker.DisplayName,
};
}

public TransactionRecord Promotional(decimal amount, string description)
{
return new TransactionRecord
{
Name = "Promotional Credits",
Description = description,
Amount = amount,
Type = TransactionType.Promotional,
From = Accounts.BunnyBooker.DisplayName,
To = Accounts.Usable.DisplayName,
};
}
}
3 changes: 3 additions & 0 deletions Domain/Wallet/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public interface IWalletRepository
// increase usable
Task<Result<WalletPrincipal?>> Deposit(Guid id, decimal amount);

// decrease usable
Task<Result<WalletPrincipal?>> Collect(Guid id, decimal amount);

// usable -> booking reserve
Task<Result<WalletPrincipal?>> BookStart(Guid id, decimal amount);

Expand Down

0 comments on commit 8cf8e66

Please sign in to comment.