using System.Net.Http.Json; using System.Text.Json; namespace Synor.Sdk.Economics; /// /// Synor Economics SDK client for C#. /// Pricing, billing, staking, and discount management. /// public class SynorEconomics : IDisposable { private readonly EconomicsConfig _config; private readonly HttpClient _httpClient; private readonly JsonSerializerOptions _jsonOptions; private bool _disposed; public SynorEconomics(EconomicsConfig config) { _config = config; _httpClient = new HttpClient { BaseAddress = new Uri(config.Endpoint), Timeout = config.Timeout }; _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}"); _httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0"); _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true }; } // ==================== Pricing Operations ==================== public async Task> GetPricingAsync(ServiceType? service = null, CancellationToken ct = default) { var path = service.HasValue ? $"/pricing?service={service.Value.ToString().ToLower()}" : "/pricing"; var r = await GetAsync(path, ct); return r.Pricing ?? new List(); } public async Task GetPriceAsync(ServiceType service, UsageMetrics usage, CancellationToken ct = default) { var body = new { service = service.ToString().ToLower(), usage }; return await PostAsync("/pricing/calculate", body, ct); } public async Task EstimateCostAsync(List plan, CancellationToken ct = default) => await PostAsync("/pricing/estimate", new { plan }, ct); // ==================== Billing Operations ==================== public async Task GetUsageAsync(BillingPeriod? period = null, CancellationToken ct = default) { var path = period.HasValue ? $"/billing/usage?period={period.Value.ToString().ToLower()}" : "/billing/usage"; return await GetAsync(path, ct); } public async Task> GetInvoicesAsync(CancellationToken ct = default) { var r = await GetAsync("/billing/invoices", ct); return r.Invoices ?? new List(); } public async Task GetInvoiceAsync(string invoiceId, CancellationToken ct = default) => await GetAsync($"/billing/invoices/{Uri.EscapeDataString(invoiceId)}", ct); public async Task GetBalanceAsync(CancellationToken ct = default) => await GetAsync("/billing/balance", ct); // ==================== Staking Operations ==================== public async Task StakeAsync(string amount, int durationDays = 0, CancellationToken ct = default) { var body = new Dictionary { ["amount"] = amount }; if (durationDays > 0) body["durationDays"] = durationDays; return await PostAsync("/staking/stake", body, ct); } public async Task UnstakeAsync(string stakeId, CancellationToken ct = default) => await PostAsync($"/staking/{Uri.EscapeDataString(stakeId)}/unstake", new { }, ct); public async Task> GetStakesAsync(CancellationToken ct = default) { var r = await GetAsync("/staking", ct); return r.Stakes ?? new List(); } public async Task GetStakeAsync(string stakeId, CancellationToken ct = default) => await GetAsync($"/staking/{Uri.EscapeDataString(stakeId)}", ct); public async Task GetStakingRewardsAsync(CancellationToken ct = default) => await GetAsync("/staking/rewards", ct); public async Task ClaimRewardsAsync(CancellationToken ct = default) => await PostAsync("/staking/rewards/claim", new { }, ct); // ==================== Discount Operations ==================== public async Task ApplyDiscountAsync(string code, CancellationToken ct = default) => await PostAsync("/discounts/apply", new { code }, ct); public async Task RemoveDiscountAsync(string code, CancellationToken ct = default) => await DeleteAsync($"/discounts/{Uri.EscapeDataString(code)}", ct); public async Task> GetDiscountsAsync(CancellationToken ct = default) { var r = await GetAsync("/discounts", ct); return r.Discounts ?? new List(); } // ==================== Lifecycle ==================== public async Task HealthCheckAsync(CancellationToken ct = default) { try { var r = await GetAsync("/health", ct); return r.Status == "healthy"; } catch { return false; } } public bool IsClosed => _disposed; public void Dispose() { if (!_disposed) { _httpClient.Dispose(); _disposed = true; } GC.SuppressFinalize(this); } // ==================== Private HTTP Methods ==================== private async Task GetAsync(string path, CancellationToken ct) => await ExecuteAsync(async () => { var r = await _httpClient.GetAsync(path, ct); await EnsureSuccessAsync(r); return await r.Content.ReadFromJsonAsync(_jsonOptions, ct)!; }); private async Task PostAsync(string path, object body, CancellationToken ct) => await ExecuteAsync(async () => { var r = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct); await EnsureSuccessAsync(r); return await r.Content.ReadFromJsonAsync(_jsonOptions, ct)!; }); private async Task DeleteAsync(string path, CancellationToken ct) => await ExecuteAsync(async () => { var r = await _httpClient.DeleteAsync(path, ct); await EnsureSuccessAsync(r); return true; }); private async Task ExecuteAsync(Func> op) { Exception? err = null; for (int i = 0; i < _config.Retries; i++) { try { return await op(); } catch (Exception ex) { err = ex; if (i < _config.Retries - 1) await Task.Delay(1000 << i); } } throw err!; } private async Task EnsureSuccessAsync(HttpResponseMessage r) { if (!r.IsSuccessStatusCode) { var c = await r.Content.ReadAsStringAsync(); var e = JsonSerializer.Deserialize>(c, _jsonOptions); throw new EconomicsException(e?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)r.StatusCode}", e?.GetValueOrDefault("code")?.ToString(), (int)r.StatusCode); } } }