using System.Net.Http.Json; using System.Reflection; namespace LMEMappeCatastaliApp.Services; public sealed class PlayPurchaseVerificationService { private const string EndpointMetadataKey = "PlayBillingVerificationUrl"; private const string RequiredMetadataKey = "PlayBillingVerificationRequired"; private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(20) }; public bool IsConfigured => VerificationEndpoint is not null; public bool IsRequired => ReadMetadata(RequiredMetadataKey).Equals("true", StringComparison.OrdinalIgnoreCase); private static Uri? VerificationEndpoint { get { var value = ReadMetadata(EndpointMetadataKey); return Uri.TryCreate(value, UriKind.Absolute, out var endpoint) && endpoint.Scheme == Uri.UriSchemeHttps ? endpoint : null; } } public async Task VerifyAsync( string purchaseToken, CancellationToken cancellationToken = default) { var endpoint = VerificationEndpoint; if (endpoint is null) { return PlayPurchaseVerificationResult.NotConfigured(); } try { using var response = await _httpClient.PostAsJsonAsync(endpoint, new { packageName = AppInfo.Current.PackageName, productId = EntitlementService.ProductId, purchaseToken }, cancellationToken); if (!response.IsSuccessStatusCode) { return PlayPurchaseVerificationResult.Failed( $"Verifica acquisto non disponibile ({(int)response.StatusCode})."); } var result = await response.Content.ReadFromJsonAsync(cancellationToken); return result is null ? PlayPurchaseVerificationResult.Failed("Risposta di verifica acquisto non valida.") : new(true, true, result.Active, result.ExpiresAt, result.Message ?? string.Empty); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { return PlayPurchaseVerificationResult.Failed("Il server di verifica acquisti non ha risposto in tempo."); } catch (Exception error) { return PlayPurchaseVerificationResult.Failed( $"Verifica acquisto non disponibile: {error.GetBaseException().Message}"); } } private static string ReadMetadata(string key) => typeof(PlayPurchaseVerificationService).Assembly .GetCustomAttributes() .FirstOrDefault(attribute => attribute.Key == key)?.Value?.Trim() ?? string.Empty; private sealed record VerificationResponse(bool Active, DateTimeOffset? ExpiresAt, string? Message); } public sealed record PlayPurchaseVerificationResult( bool Configured, bool Verified, bool Active, DateTimeOffset? ExpiresAt, string Message) { public static PlayPurchaseVerificationResult NotConfigured() => new(false, false, false, null, "Server di verifica acquisti non configurato."); public static PlayPurchaseVerificationResult Failed(string message) => new(true, false, false, null, message); }