using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using RadicadeServer.API.Notifications; using RadicadeServer.DB; using RadicadeServer.Extensions; using RadicadeServer.RecNet.Enums.Messages; using RadicadeServer.RecNet.Accounts; using RadicadeServer.RecNet.Auth; using RadicadeServer.RecNet.Enums.Store; using RadicadeServer.RecNet.Notifications; using RadicadeServer.RecNet.Responses; using RadicadeServer.RecNet.Storefront; namespace RadicadeServer.API.Storefront { [Route("api/storefronts/")] [Route("econ/api/storefronts/")] public class PurchaseAPI(RecDBContext _db) : Controller { [HttpPost("v1/buyItem"), Authorize] public async Task BuyItem([FromBody] RequestPurchaseItemDTO? request) => await ProcessPurchase(request); [HttpPost("v1/buyTier"), Authorize] public async Task BuyTier([FromBody] RequestPurchaseItemDTO? request) => await ProcessPurchase(request); [HttpPost("v1/buyElite"), Authorize] public async Task BuyElite([FromBody] RequestPurchaseItemDTO? request) => await ProcessPurchase(request); [HttpGet("v2/buyInvention"), Authorize] public async Task BuyInvention([FromQuery] long inventionId, [FromQuery] long requestedPrice) => await ProcessInventionPurchase(inventionId, requestedPrice); private async Task ProcessPurchase(RequestPurchaseItemDTO? request) { var userId = User.GetUserId(); if (userId == null) return Unauthorized(); if (request == null) { return BadRequest(new { error = "invalid_request" }); } var outcome = await StorefrontShared.ExecutePurchaseAsync( _db, userId.Value, request.StorefrontType, request.PurchasableItemId, request.CurrencyType, request.RequestedPrice, request.Gift); if (outcome.Response != null) return Ok(outcome.Response); return outcome.Error switch { "cannot_gift_self" or "invalid_recipient" => BadRequest(new { error = outcome.Error, message = outcome.ErrorDetail }), _ => StatusCode(StatusCodes.Status500InternalServerError, new { error = outcome.Error, message = outcome.ErrorDetail }) }; } private async Task ProcessInventionPurchase(long inventionId, long requestedPrice) { var userId = User.GetUserId(); if (userId == null) return Unauthorized(); var invention = await _db.Inventions.FirstOrDefaultAsync(x => x.InventionId == inventionId); if (invention == null) return NotFound(); var currency = await _db.Currency.FirstOrDefaultAsync(x => x.AccountId == userId); if (currency == null) return NotFound(); var unlocks = await _db.Unlocks.FirstOrDefaultAsync(x => x.AccountId == userId); if (unlocks == null) { unlocks = new PlayerUnlocks { AccountId = userId.Value }; await _db.Unlocks.AddAsync(unlocks); } var owned = ReadOwnedInventions(unlocks); if (owned.Contains(inventionId)) return Ok(new Success(false, "Invention is already owned.", null)); var price = invention.Price ?? 0; if (requestedPrice != price) { return Ok(new Success(false, "Invalid requested price.", null)); } var (balance, delta, sufficient) = TryDeductCurrency(currency, CurrencyType.RecCenterTokens, price); if (!sufficient) { return Ok(new Success(false, "You do not have enough tokens for this invention.", null)); } owned.Add(inventionId); unlocks.Inventions = owned.ToJson(); await _db.SaveChangesAsync(); var response = new InventionPurchaseResponseDTO.BalanceResponse { Balance = balance, BalanceAddType = StorefrontBalanceAddTypes.CommercePurchase, CurrencyType = CurrencyType.RecCenterTokens, Delta = delta, Result = new List { new InventionPurchaseResponseDTO.BalanceResponse.InventionData{ Invention = invention.ToInvention(), Status = PurchaseStatus.OK } } }; var purchased = new InventionPurchaseResponseDTO { InventionResponse = invention.ToInvention(), BalanceUpdateResponse = response }; await Notify.SendNotify((long)userId, new Notification { Id = NotificationType.StorefrontBalancePurchase, Msg = purchased }); await Notify.SendNotify((long)userId, new Notification { Id = NotificationType.StorefrontBalanceUpdate, Msg = response }); var buyerProfile = await _db.Profiles.AsNoTracking().FirstOrDefaultAsync(x => x.accountId == userId); if (buyerProfile != null) await Notify.SendNotify((long)userId, new Notification { Id = NotificationType.RefreshLogin, Msg = buyerProfile }); return Ok(new Success(true, null, purchased)); } private static List ReadOwnedInventions(PlayerUnlocks unlocks) { if (string.IsNullOrWhiteSpace(unlocks.Inventions) || unlocks.Inventions == "null") return new List(); try { return unlocks.Inventions.FromJson>() ?? new List(); } catch { return new List(); } } private static (long balance, long delta, bool sufficient) TryDeductCurrency( RecNet.Accounts.Currency currency, CurrencyType type, long cost) { long balance = type switch { CurrencyType.RecCenterTokens => currency.RecTokens, CurrencyType.LaserTagTickets => currency.LaserTagTickets, CurrencyType.LostSkullsGold => currency.Gold, CurrencyType.DraculaSilver => currency.DraculaSilver, _ => 0 }; if (balance < cost) return (balance, 0, false); switch (type) { case CurrencyType.RecCenterTokens: currency.RecTokens -= cost; break; case CurrencyType.LaserTagTickets: currency.LaserTagTickets -= cost; break; case CurrencyType.LostSkullsGold: currency.Gold -= cost; break; case CurrencyType.DraculaSilver: currency.DraculaSilver -= cost; break; } return (balance - cost, cost, true); } } }