using System.Text.Json; using Prospect.Server.Api.Models.Data; using Prospect.Server.Api.Services.Auth.Extensions; using Prospect.Server.Api.Services.CloudScript.Models; using Prospect.Server.Api.Services.CloudScript.Models.Data; using Prospect.Server.Api.Services.UserData; namespace Prospect.Server.Api.Services.CloudScript.Functions; // Config d'une police d'assurance (TitleData "InventoryInsurance"). public class InventoryInsurancePolicy { public string InsuranceId { get; set; } public double Cost { get; set; } public double Payout { get; set; } } [CloudScriptFunction("EnterMatchmakingStation")] public class EnterMatchmakingStationFunction : ICloudScriptFunction { private readonly IHttpContextAccessor _httpContextAccessor; private readonly UserDataService _userDataService; private readonly TitleDataService _titleDataService; private readonly ILogger _logger; public EnterMatchmakingStationFunction( IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService, ILogger logger) { _httpContextAccessor = httpContextAccessor; _userDataService = userDataService; _titleDataService = titleDataService; _logger = logger; } public async Task ExecuteAsync(FYEnterMatchAzureFunction request) { // Au déploiement, le client peut demander d'assurer une partie de l'inventaire. var insurance = request.PurchaseInventoryInsurances; var context = _httpContextAccessor.HttpContext; if (context != null && insurance?.ItemIds is { Count: > 0 }) { try { await ApplyInsuranceAsync(context.User.FindAuthUserId(), insurance); } catch (Exception e) { // Ne jamais bloquer le déploiement à cause de l'assurance. _logger.LogWarning(e, "Failed to apply inventory insurance on deploy"); } } return new FYEnterMatchmakingResult { Success = true, ErrorMessage = "", SingleplayerStation = true, // NOTE: This will always travel to single player station IsMatchTravel = false, }; } // Marque les items choisis comme assurés (Insurance + propriétaire) et débite la // prime en InsuranceCurrency selon le coût de la police (TitleData InventoryInsurance). private async Task ApplyInsuranceAsync(string userId, FYPurchaseInventoryInsuranceRequest req) { var titleData = _titleDataService.Find(new List { "InventoryInsurance" }); var policies = JsonSerializer.Deserialize>(titleData["InventoryInsurance"]); var insuranceId = string.IsNullOrEmpty(req.InsuranceId) ? "Default" : req.InsuranceId; if (policies == null || !policies.TryGetValue(insuranceId, out var policy)) { _logger.LogWarning("Unknown insurance policy {InsuranceId}", insuranceId); return; } var userData = await _userDataService.FindAsync(userId, userId, new List { "Inventory", "Balance" }); var inventory = JsonSerializer.Deserialize>(userData["Inventory"].Value); var balance = JsonSerializer.Deserialize(userData["Balance"].Value); var toInsure = new HashSet(req.ItemIds); var insuredCount = 0; foreach (var item in inventory) { if (!toInsure.Contains(item.ItemId)) continue; item.Insurance = insuranceId; item.InsuranceOwnerPlayfabId = userId; insuredCount++; } if (insuredCount == 0) return; // Prime = coût de la police × nombre d'items assurés (arrondi au supérieur), // plafonnée au solde disponible pour ne jamais passer en négatif. var premium = (int)Math.Ceiling(policy.Cost * insuredCount); premium = Math.Min(premium, balance.InsuranceCurrency); balance.InsuranceCurrency -= premium; await _userDataService.UpdateAsync(userId, userId, new Dictionary { ["Inventory"] = JsonSerializer.Serialize(inventory), ["Balance"] = JsonSerializer.Serialize(balance), }); _logger.LogInformation("Insured {Count} item(s) under policy {Policy} for user {User} (premium {Premium})", insuredCount, insuranceId, userId, premium); } }