using System.Net.Http.Headers; using System.Text; using System.Text.Json; using AMREZ.EOP.Abstractions.Infrastructures.Integrations.SCB.Clients; using AMREZ.EOP.Contracts.DTOs.Integrations.SCB.OAuth; using AMREZ.EOP.Contracts.DTOs.Integrations.SCB.SlipVerification; using AMREZ.EOP.Domain.Shared.Payments; namespace AMREZ.EOP.Infrastructures.Integrations.SCB.Clients; public sealed class ScbSlipClient : IScbSlipClient { private readonly HttpClient _http; private readonly SCBOptions _opts; private readonly JsonSerializerOptions _json = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull }; private string? _token; private DateTimeOffset _exp; private readonly SemaphoreSlim _gate = new(1,1); public ScbSlipClient(HttpClient http, SCBOptions opts) { _http = http; _opts = opts; } public async Task VerifyAsync(string transRef, string sendingBank, CancellationToken ct) { var bearer = await GetTokenAsync(ct); var path = $"payment/billpayment/transactions/{Uri.EscapeDataString(transRef)}?sendingBank={Uri.EscapeDataString(sendingBank)}"; using var req = new HttpRequestMessage(HttpMethod.Get, path); AddHeaders(req.Headers, bearer); using var res = await _http.SendAsync(req, ct); var body = await res.Content.ReadAsStringAsync(ct); if (!res.IsSuccessStatusCode) return null; return JsonSerializer.Deserialize(body, _json); } private async Task GetTokenAsync(CancellationToken ct) { if (!string.IsNullOrEmpty(_token) && _exp > DateTimeOffset.UtcNow.AddSeconds(30)) return _token!; await _gate.WaitAsync(ct); try { if (!string.IsNullOrEmpty(_token) && _exp > DateTimeOffset.UtcNow.AddSeconds(30)) return _token!; var json = JsonSerializer.Serialize(new OAuthRequest(_opts.SCBApplicationKey, _opts.SCBApplicationSecret), _json); using var req = new HttpRequestMessage(HttpMethod.Post, "oauth/token") { Content = new StringContent(json, Encoding.UTF8, "application/json") }; AddHeaders(req.Headers, null); using var res = await _http.SendAsync(req, ct); var body = await res.Content.ReadAsStringAsync(ct); res.EnsureSuccessStatusCode(); var parsed = JsonSerializer.Deserialize(body, _json) ?? throw new UnauthorizedAccessException("OAuth parse failed"); if (parsed.Status?.Code != 1000) throw new UnauthorizedAccessException(parsed.Status?.Description ?? "OAuth not OK"); _token = parsed.Data?.AccessToken ?? throw new UnauthorizedAccessException("No token"); var ttl = parsed.Data?.ExpiresIn ?? 300; _exp = DateTimeOffset.UtcNow.AddSeconds(ttl); return _token!; } finally { _gate.Release(); } } private void AddHeaders(HttpRequestHeaders h, string? bearer) { h.TryAddWithoutValidation("accept-language", _opts.DefaultLanguage); h.TryAddWithoutValidation("requestUId", Guid.NewGuid().ToString()); h.TryAddWithoutValidation("resourceOwnerId", _opts.SCBResourceOwnerId); if (!string.IsNullOrEmpty(bearer)) h.Authorization = new AuthenticationHeaderValue("Bearer", bearer); } }