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);
}
}
}