using System.Net.Http; using System.Text; namespace AyCode.Services.Nav; /// /// Általános NAV-adatszolgáltatás base osztály. Egy helyen kezeli a NAV-rendszerekre közös send-flow-t: /// auth-header feltöltés → XML szerializáció → HTTP POST → válasz deszerializáció → sikeresség-ellenőrzés. /// A konkrét NAV API (EKÁER, Online Számla, …) ebből származik, és csak a saját namespace-ét / endpoint-ját /// adja meg a -ban; a request/response típusait pedig generikus paraméterként. /// /// /// Platform-független: a -et a hívó injektálja (szerver vagy MAUI), így a /// réteg nem köthető szerverhez. Böngésző-WASM-ből CORS miatt nem hívható közvetlenül — ott a hívás SignalR-en /// át a szerverre delegálandó. /// Redundancia-mentes: a send-flow + auth-hash itt él egyszer; egy újabb NAV-integráció csak a saját /// generált modelljeit és a / implementációt adja. /// /// A NAV request típusa (az API generált root request-je, ami -et implementál). /// A NAV response típusa (az API generált root response-a, ami -t implementál). public abstract class NavReportServiceBase where TRequest : class, INavRequest where TResponse : class, INavResponse { private readonly HttpClient _httpClient; private readonly INavCredentials _credentials; protected NavReportServiceBase(HttpClient httpClient, INavCredentials credentials) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); } /// /// A NAV művelet relatív útvonala a -hez képest, /// pl. "TradeCardManagementService/customer/manageTradeCards". /// protected abstract string OperationPath { get; } /// /// Elküldi a request-et a NAV-nak: feltölti az auth-headert, szerializál, POST-ol, deszerializál, és /// ellenőrzi a sikerességet. Hibánál -t dob. /// protected async Task SendAsync(TRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ApplyAuthentication(request); var requestXml = NavXmlHelper.Serialize(request); var url = $"{_credentials.BaseUrl.TrimEnd('/')}/{OperationPath}"; // A NAV kötelezően text/xml content-type-ot ÉS accept-et vár (nem application/xml) — PDF §3.3. using var content = new StringContent(requestXml, Encoding.UTF8, "text/xml"); using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url) { Content = content }; httpRequest.Headers.Accept.ParseAdd("text/xml"); using var httpResponse = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); var responseXml = await httpResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); if (!httpResponse.IsSuccessStatusCode) { throw new NavReportException( $"NAV HTTP {(int)httpResponse.StatusCode} ({httpResponse.ReasonPhrase}). Body: {Truncate(responseXml, 2000)}", httpStatusCode: (int)httpResponse.StatusCode); } TResponse response; try { response = NavXmlHelper.Deserialize(responseXml); } catch (Exception ex) { throw new NavReportException($"NAV válasz deszerializáció sikertelen. Body: {Truncate(responseXml, 2000)}", innerException: ex); } var result = response.Result; if (!result.IsSuccess) { throw new NavReportException( result.Message ?? "NAV adatszolgáltatás sikertelen.", reasonCode: result.ReasonCode); } return response; } /// /// Feltölti a request base- és auth-headerét: egyedi requestId + timestamp, majd a credentials alapján a /// felhasználó/adószám és a két SHA-512 hash (passwordHash, requestSignature). API-független, az /// interfészen keresztül. /// private void ApplyAuthentication(TRequest request) { var requestId = GenerateRequestId(); // A NAV az aláíráshoz a timestamp UTC-megfelelőjét várja (yyyyMMddHHmmss). UTC-t használunk, így a // header timestamp 'Z'-vel (egyértelmű UTC) szerializálódik, és a signature ugyanazzal az értékkel // képződik. (eKAERManagementService_2.2.pdf §2.2.3 — lásd Nav/docs/EKAER_INTERFACE.md.) var timestamp = DateTime.UtcNow; var header = request.RequestHeader; header.RequestId = requestId; header.Timestamp = timestamp; var user = request.UserHeader; user.User = _credentials.User; user.TaxNumber = _credentials.TaxNumber; user.PasswordHash = NavAuthHelper.ComputePasswordHash(_credentials.Password); user.RequestSignature = NavAuthHelper.ComputeRequestSignature(requestId, timestamp, _credentials.SigningKey); } /// Egyedi requestId. EKÁER BasicHeaderType.requestId pattern: [+a-zA-Z0-9_/=]{1,50}. private static string GenerateRequestId() => Guid.NewGuid().ToString("N"); private static string Truncate(string value, int max) => string.IsNullOrEmpty(value) || value.Length <= max ? value : value[..max] + "…"; }