Check in rewards NSD & payment

This commit is contained in:
2025-05-15 22:03:51 +08:00
parent d7d4fde06a
commit 0db003abc2
16 changed files with 6968 additions and 53 deletions

View File

@ -1,8 +1,14 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Sphere.Developer;
using NodaTime;
namespace DysonNetwork.Sphere.Wallet;
public class WalletCurrency
{
public const string SourcePoint = "points";
}
public enum OrderStatus
{
Unpaid,
@ -25,6 +31,8 @@ public class Order : ModelBase
public Wallet PayeeWallet { get; set; } = null!;
public Guid? TransactionId { get; set; }
public Transaction? Transaction { get; set; }
public Guid? IssuerAppId { get; set; }
public CustomApp? IssuerApp { get; set; }
}
public enum TransactionType

View File

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using NodaTime;
namespace DysonNetwork.Sphere.Wallet;
@ -20,6 +21,36 @@ public class PaymentService(AppDatabase db, WalletService wat)
return order;
}
public async Task<Transaction> CreateTransactionWithAccountAsync(
Guid? payerAccountId,
Guid? payeeAccountId,
string currency,
decimal amount,
string? remarks = null,
TransactionType type = TransactionType.System
)
{
Wallet? payer = null, payee = null;
if (payerAccountId.HasValue)
payer = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payerAccountId.Value);
if (payeeAccountId.HasValue)
payee = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payeeAccountId.Value);
if (payer == null && payerAccountId.HasValue)
throw new ArgumentException("Payer account was specified, but wallet was not found");
if (payee == null && payeeAccountId.HasValue)
throw new ArgumentException("Payee account was specified, but wallet was not found");
return await CreateTransactionAsync(
payer?.Id,
payee?.Id,
currency,
amount,
remarks,
type
);
}
public async Task<Transaction> CreateTransactionAsync(
Guid? payerWalletId,
Guid? payeeWalletId,
@ -29,6 +60,10 @@ public class PaymentService(AppDatabase db, WalletService wat)
TransactionType type = TransactionType.System
)
{
if (payerWalletId == null && payeeWalletId == null)
throw new ArgumentException("At least one wallet must be specified.");
if (amount <= 0) throw new ArgumentException("Cannot create transaction with negative or zero amount.");
var transaction = new Transaction
{
PayerWalletId = payerWalletId,
@ -41,26 +76,28 @@ public class PaymentService(AppDatabase db, WalletService wat)
if (payerWalletId.HasValue)
{
var payerPocket = await wat.GetOrCreateWalletPocketAsync(
(await db.Wallets.FindAsync(payerWalletId.Value))!.AccountId,
currency);
var (payerPocket, isNewlyCreated) =
await wat.GetOrCreateWalletPocketAsync(payerWalletId.Value, currency);
if (payerPocket.Amount < amount)
{
if (isNewlyCreated || payerPocket.Amount < amount)
throw new InvalidOperationException("Insufficient funds");
}
payerPocket.Amount -= amount;
await db.WalletPockets
.Where(p => p.Id == payerPocket.Id && p.Amount >= amount)
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.Amount, p => p.Amount - amount));
}
if (payeeWalletId.HasValue)
{
var payeeWallet = await db.Wallets.FindAsync(payeeWalletId.Value);
var payeePocket = await wat.GetOrCreateWalletPocketAsync(
payeeWallet!.AccountId,
currency);
var (payeePocket, isNewlyCreated) =
await wat.GetOrCreateWalletPocketAsync(payeeWalletId.Value, currency, amount);
payeePocket.Amount += amount;
if (!isNewlyCreated)
await db.WalletPockets
.Where(p => p.Id == payeePocket.Id)
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.Amount, p => p.Amount + amount));
}
db.PaymentTransactions.Add(transaction);
@ -158,21 +195,22 @@ public class PaymentService(AppDatabase db, WalletService wat)
return (order, refundTransaction);
}
public async Task<Transaction> TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency, decimal amount)
public async Task<Transaction> TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency,
decimal amount)
{
var payerWallet = await wat.GetWalletAsync(payerAccountId);
if (payerWallet == null)
{
throw new InvalidOperationException($"Payer wallet not found for account {payerAccountId}");
}
var payeeWallet = await wat.GetWalletAsync(payeeAccountId);
if (payeeWallet == null)
{
throw new InvalidOperationException($"Payee wallet not found for account {payeeAccountId}");
}
return await CreateTransactionAsync(
payerWallet.Id,
payeeWallet.Id,

View File

@ -0,0 +1,63 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Sphere.Wallet;
[ApiController]
[Route("/wallets")]
public class WalletController(AppDatabase db, WalletService ws) : ControllerBase
{
[HttpPost]
[Authorize]
public async Task<ActionResult<Wallet>> CreateWallet()
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
try
{
var wallet = await ws.CreateWalletAsync(currentUser.Id);
return Ok(wallet);
}
catch (Exception err)
{
return BadRequest(err.Message);
}
}
[HttpGet]
[Authorize]
public async Task<ActionResult<Wallet>> GetWallet()
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var wallet = await ws.GetWalletAsync(currentUser.Id);
if (wallet is null) return NotFound("Wallet was not found, please create one first.");
return Ok(wallet);
}
[HttpGet("transactions")]
[Authorize]
public async Task<ActionResult<List<Transaction>>> GetTransactions(
[FromQuery] int offset = 0, [FromQuery] int take = 20
)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var query = db.PaymentTransactions.AsQueryable()
.Include(t => t.PayeeWallet)
.Include(t => t.PayerWallet)
.Where(t => (t.PayeeWallet != null && t.PayeeWallet.AccountId == currentUser.Id) ||
(t.PayerWallet != null && t.PayerWallet.AccountId == currentUser.Id));
var transactionCount = await query.CountAsync();
var transactions = await query
.Skip(offset)
.Take(take)
.ToListAsync();
Response.Headers["X-Total"] = transactionCount.ToString();
return Ok(transactions);
}
}

View File

@ -10,45 +10,40 @@ public class WalletService(AppDatabase db)
.Include(w => w.Pockets)
.FirstOrDefaultAsync(w => w.AccountId == accountId);
}
public async Task<Wallet> CreateWalletAsync(Guid accountId)
{
var wallet = new Wallet
var existingWallet = await db.Wallets.FirstOrDefaultAsync(w => w.AccountId == accountId);
if (existingWallet != null)
{
AccountId = accountId
};
throw new InvalidOperationException($"Wallet already exists for account {accountId}");
}
var wallet = new Wallet { AccountId = accountId };
db.Wallets.Add(wallet);
await db.SaveChangesAsync();
return wallet;
}
public async Task<WalletPocket> GetOrCreateWalletPocketAsync(Guid accountId, string currency)
public async Task<(WalletPocket wallet, bool isNewlyCreated)> GetOrCreateWalletPocketAsync(
Guid walletId,
string currency,
decimal? initialAmount = null
)
{
var wallet = await db.Wallets
.Include(w => w.Pockets)
.FirstOrDefaultAsync(w => w.AccountId == accountId);
var pocket = await db.WalletPockets.FirstOrDefaultAsync(p => p.Currency == currency && p.WalletId == walletId);
if (pocket != null) return (pocket, false);
if (wallet == null)
{
throw new InvalidOperationException($"Wallet not found for account {accountId}");
}
var pocket = wallet.Pockets.FirstOrDefault(p => p.Currency == currency);
if (pocket != null) return pocket;
pocket = new WalletPocket
{
Currency = currency,
Amount = 0,
WalletId = wallet.Id
Amount = initialAmount ?? 0,
WalletId = walletId
};
wallet.Pockets.Add(pocket);
await db.SaveChangesAsync();
return pocket;
db.WalletPockets.Add(pocket);
return (pocket, true);
}
}