Add kaigen debit credit for backtest

This commit is contained in:
2025-07-15 10:31:21 +07:00
parent f1c7259bc6
commit 11778aa2a4
7 changed files with 326 additions and 13 deletions

View File

@@ -0,0 +1,165 @@
using System.Net.Http.Json;
using System.Text.Json;
using Managing.Application.Abstractions.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Nethereum.Signer;
namespace Managing.Infrastructure.Evm.Services;
/// <summary>
/// Configuration settings for Kaigen credit management service.
/// The PrivateKey should be set via the KAIGEN_PRIVATE_KEY environment variable for security.
/// </summary>
public class KaigenSettings
{
public string BaseUrl { get; set; } = "https://api.kaigen.managing.live";
public string DebitEndpoint { get; set; } = "/api/credits/debit";
public string RefundEndpoint { get; set; } = "/api/credits/refund";
public string PrivateKey { get; set; } = string.Empty;
}
public class KaigenService : IKaigenService
{
private readonly HttpClient _httpClient;
private readonly KaigenSettings _settings;
private readonly ILogger<KaigenService> _logger;
private readonly JsonSerializerOptions _jsonOptions;
public KaigenService(IOptions<KaigenSettings> options, ILogger<KaigenService> logger)
{
_httpClient = new HttpClient();
_settings = options.Value;
_logger = logger;
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
// Validate required settings
if (string.IsNullOrEmpty(_settings.PrivateKey))
{
throw new InvalidOperationException("Kaigen PrivateKey is not configured. Please set the KAIGEN_PRIVATE_KEY environment variable.");
}
if (string.IsNullOrEmpty(_settings.BaseUrl))
{
throw new InvalidOperationException("Kaigen BaseUrl is not configured.");
}
}
public async Task<string> DebitUserCreditsAsync(string userName, decimal debitAmount)
{
try
{
var requestId = Guid.NewGuid().ToString();
// Create the payload for signing
var payload = new
{
RequestId = requestId,
UserName = userName,
DebitAmount = debitAmount
};
// Create the message to sign (concatenate the values)
var message = $"{requestId}{userName}{debitAmount}";
// Sign the message
var signature = SignMessage(message, _settings.PrivateKey);
// Create the request payload
var requestPayload = new
{
requestId = requestId,
userName = userName,
debitAmount = debitAmount,
signature = signature
};
_logger.LogInformation("Debiting {Amount} credits for user {UserName} with request ID {RequestId}",
debitAmount, userName, requestId);
var response = await _httpClient.PutAsJsonAsync(
$"{_settings.BaseUrl}{_settings.DebitEndpoint}",
requestPayload,
_jsonOptions);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("Failed to debit credits. Status: {StatusCode}, Error: {Error}",
response.StatusCode, errorContent);
throw new Exception($"Failed to debit credits: {response.StatusCode} - {errorContent}");
}
var result = await response.Content.ReadFromJsonAsync<KaigenResponse>(_jsonOptions);
_logger.LogInformation("Successfully debited {Amount} credits for user {UserName}",
debitAmount, userName);
return requestId;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error debiting credits for user {UserName}", userName);
throw;
}
}
public async Task<bool> RefundUserCreditsAsync(string requestId, string userName)
{
try
{
// Create the message to sign (concatenate the values)
var message = $"{requestId}{userName}";
// Sign the message
var signature = SignMessage(message, _settings.PrivateKey);
// Create the request payload
var requestPayload = new
{
requestId = requestId,
userName = userName,
signature = signature
};
_logger.LogInformation("Refunding credits for user {UserName} with request ID {RequestId}",
userName, requestId);
var response = await _httpClient.PutAsJsonAsync(
$"{_settings.BaseUrl}{_settings.RefundEndpoint}",
requestPayload,
_jsonOptions);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("Failed to refund credits. Status: {StatusCode}, Error: {Error}",
response.StatusCode, errorContent);
return false;
}
_logger.LogInformation("Successfully refunded credits for user {UserName}", userName);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error refunding credits for user {UserName}", userName);
return false;
}
}
private string SignMessage(string message, string privateKey)
{
var signer = new EthereumMessageSigner();
var signature = signer.EncodeUTF8AndSign(message, new EthECKey(privateKey));
return signature;
}
private class KaigenResponse
{
public bool Success { get; set; }
public string Message { get; set; }
}
}