#nullable enable using System.Text.Json; using Baya.Application.Contracts.Common; namespace Baya.Infrastructure.CrossCutting.Seams.Real; /// /// Real over the Finnotech استعلام شبا owner ↔ national-id inquiry /// (refinement-phase-8, 5.3 — the b13 first-payout money-mule gate). Selected by /// Seams:BankOwnership:Provider = finnotech. Resolves the IBAN's registered owner and matches it against /// the nurse's national id; persists the vendor track id. /// public sealed class FinnotechBankAccountOwnershipVerifier(FinnotechClient client) : IBankAccountOwnershipVerifier { public async Task VerifyOwnershipAsync(string iban, string? nurseNationalId, CancellationToken cancellationToken = default) { var trackId = FinnotechClient.NewTrackId(); var vendorRef = $"FINNOTECH-SHEBA-{trackId}"; var path = $"oak/v2/clients/{client.ClientId}/ibanToNid" + $"?iban={Uri.EscapeDataString(Normalize(iban))}&trackId={trackId}"; var (document, _) = await client.GetAsync(path, cancellationToken); using var _doc = document; var root = document.RootElement; string ownerName = string.Empty; string? ownerNid = null; if (root.TryGetProperty("result", out var result)) { if (result.TryGetProperty("name", out var name) && name.ValueKind == JsonValueKind.String) ownerName = name.GetString() ?? string.Empty; if (result.TryGetProperty("nationalCode", out var nid) && nid.ValueKind == JsonValueKind.String) ownerNid = nid.GetString(); } // Ownership matches when the account's registered national code equals the nurse's. If the vendor did not // return a national code we cannot assert a match — fail closed (matched = false), since this is the gate. var matched = !string.IsNullOrEmpty(ownerNid) && !string.IsNullOrEmpty(nurseNationalId) && string.Equals(ownerNid, nurseNationalId, StringComparison.Ordinal); return new OwnershipInquiryResult(matched, ownerName, vendorRef); } private static string Normalize(string iban) => string.IsNullOrEmpty(iban) ? string.Empty : iban.Replace(" ", string.Empty).ToUpperInvariant(); }