Files
LlamaCasty/Services/PolarLicenseService.cs

88 lines
3.0 KiB
C#

using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ytLive.Services;
/// <summary>
/// Validates LlamaCasty license keys against the Polar.sh customer-portal API.
/// No auth required for client-side validation — the endpoint is public by design.
/// </summary>
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<LicenseValidationResult> 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<PolarValidateResponse>(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; }
}