using System.Net.Http.Headers; using System.Text; using System.Text.Json; using Fitness_core_BE.DTOs.PaymentGateways; using Fitness_core_BE.Exceptions; using Fitness_core_BE.Interfaces.Services; namespace Fitness_core_BE.Services { public class PaymentServiceClient : IPaymentServiceClient { private readonly HttpClient _httpClient; private readonly string _baseUrl; private readonly string _internalKey; private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; public PaymentServiceClient(IHttpClientFactory httpClientFactory, IConfiguration configuration) { _httpClient = httpClientFactory.CreateClient(); _baseUrl = (configuration["PaymentService:BaseUrl"] ?? "http://127.0.0.1:5120").TrimEnd('/'); _internalKey = configuration["PaymentService:InternalApiKey"] ?? string.Empty; } public async Task> ListGatewaysAsync(CancellationToken cancellationToken = default) { var response = await SendAsync(HttpMethod.Get, "/api/v1/gateways", null, cancellationToken); var body = await response.Content.ReadAsStringAsync(cancellationToken); return JsonSerializer.Deserialize>(body, JsonOptions) ?? new List(); } public async Task UpsertGatewayAsync(UpsertGatewayDefinitionRequest request, CancellationToken cancellationToken = default) { var response = await SendAsync(HttpMethod.Post, "/api/v1/gateways", request, cancellationToken); var body = await response.Content.ReadAsStringAsync(cancellationToken); return JsonSerializer.Deserialize(body, JsonOptions) ?? throw new ServiceUnavailableException("Payment-Service returned an empty gateway response."); } public async Task SetGatewayEnabledAsync(Guid id, bool isEnabled, CancellationToken cancellationToken = default) { var response = await SendAsync(HttpMethod.Put, $"/api/v1/gateways/{id}/enabled", new { isEnabled }, cancellationToken); var body = await response.Content.ReadAsStringAsync(cancellationToken); return JsonSerializer.Deserialize(body, JsonOptions) ?? throw new ServiceUnavailableException("Payment-Service returned an empty gateway response."); } public async Task UpsertPlatformCredentialAsync(UpsertPlatformGatewayCredentialRequest request, CancellationToken cancellationToken = default) { await SendAsync( HttpMethod.Put, $"/api/v1/gateways/platforms/{request.PlatformId}/credentials/{request.GatewayName}", new { credentialsJson = request.CredentialsJson }, cancellationToken); } public async Task<(Guid PlatformId, string ApiKey, string HmacSecret)> ProvisionPlatformAsync( Guid clientId, string displayName, CancellationToken cancellationToken = default) { var response = await SendAsync( HttpMethod.Post, "/api/v1/platforms", new { externalClientId = clientId, displayName }, cancellationToken); var body = await response.Content.ReadAsStringAsync(cancellationToken); using var doc = JsonDocument.Parse(body); var platformId = doc.RootElement.GetProperty("platformId").GetGuid(); var apiKey = doc.RootElement.GetProperty("apiKey").GetString() ?? string.Empty; var hmacSecret = doc.RootElement.GetProperty("hmacSecret").GetString() ?? string.Empty; return (platformId, apiKey, hmacSecret); } private async Task SendAsync(HttpMethod method, string path, object? body, CancellationToken cancellationToken) { using var request = new HttpRequestMessage(method, $"{_baseUrl}{path}"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Add("X-Internal-Key", _internalKey); if (body is not null) { request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); } HttpResponseMessage response; try { response = await _httpClient.SendAsync(request, cancellationToken); } catch (HttpRequestException ex) { throw new ServiceUnavailableException("Could not reach Payment-Service.", ex); } if (!response.IsSuccessStatusCode) { var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); throw new ServiceUnavailableException($"Payment-Service call failed ({(int)response.StatusCode}): {errorBody}"); } return response; } } }