using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; namespace ytLive.Services; /// /// Validates LlamaCasty license keys against the Polar.sh customer-portal API. /// No auth required for client-side validation — the endpoint is public by design. /// public class PolarLicenseService { private const string ValidateUrl = "https://api.polar.sh/v1/customer-portal/license-keys/validate"; public const string OrganizationId = "c05fb364-b967-4f6c-adf2-8a144e46d085"; private readonly HttpClient _http; public PolarLicenseService(HttpClient? http = null) { _http = http ?? new HttpClient { Timeout = TimeSpan.FromSeconds(15) }; } public async Task ValidateAsync(string key, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(key)) return new LicenseValidationResult { IsValid = false, Error = "Key is empty" }; try { var request = new { key = key.Trim(), organization_id = OrganizationId }; var response = await _http.PostAsJsonAsync(ValidateUrl, request, ct); if (!response.IsSuccessStatusCode) { var body = await response.Content.ReadAsStringAsync(ct); return new LicenseValidationResult { IsValid = false, Error = $"Validation failed ({(int)response.StatusCode}): {body}" }; } var result = await response.Content.ReadFromJsonAsync(ct); if (result == null) return new LicenseValidationResult { IsValid = false, Error = "Empty response from Polar" }; var status = result.Status?.ToLowerInvariant(); return new LicenseValidationResult { IsValid = status == "granted", Status = status ?? "unknown", ExpiresAt = result.ExpiresAt, Error = status == "granted" ? null : $"License status: {status}" }; } catch (TaskCanceledException) { return new LicenseValidationResult { IsValid = false, Error = "Validation timed out" }; } catch (HttpRequestException ex) { return new LicenseValidationResult { IsValid = false, Error = $"Network error: {ex.Message}" }; } catch (Exception ex) { return new LicenseValidationResult { IsValid = false, Error = $"Unexpected error: {ex.Message}" }; } } private sealed class PolarValidateResponse { [JsonPropertyName("status")] public string? Status { get; set; } [JsonPropertyName("expires_at")] public DateTime? ExpiresAt { get; set; } } } public sealed class LicenseValidationResult { public bool IsValid { get; set; } public string? Status { get; set; } public DateTime? ExpiresAt { get; set; } public string? Error { get; set; } }