using System.Text.RegularExpressions; namespace AMREZ.EOP.Application.UseCases.Payments.SlipVerification.QrDecode; public static partial class TransRefParser { [GeneratedRegex(@"(?i)(?:[?&](?:transref|transactionid|txnid|transid)=)(?[-A-Za-z0-9]{10,64})", RegexOptions.Compiled | RegexOptions.IgnoreCase)] private static partial Regex QueryId(); [GeneratedRegex(@"(?[0-9]{6,14}[A-Za-z0-9]{6,40})(?![A-Za-z0-9])", RegexOptions.Compiled)] private static partial Regex FallbackShape(); public static string? TryExtract(string? raw) { if (string.IsNullOrWhiteSpace(raw)) return null; // 1) ?transRef=... ใน URL var q = QueryId().Match(raw); if (q.Success) return q.Groups["id"].Value; // 2) เดิน TLV แบบ recursive foreach (var val in EnumerateTlvValues(raw)) { // ต้องเป็น a-z0-9 ล้วน ยาวพอสมควร และมีตัวอักษรด้วย (กันเคสเป็นตัวเลขล้วน) if (val.Length is >= 18 and <= 40 && val.All(IsAlphaNum) && val.Any(char.IsLetter)) return val; } // 3) เผื่อกรณีหลุดรูปแบบ TLV var f = FallbackShape().Match(raw); return f.Success ? f.Groups["id"].Value : null; } private static IEnumerable EnumerateTlvValues(string s) { int i = 0; while (i + 4 <= s.Length) { // รูปแบบ TLV: [ID(2)][LEN(2)][VALUE(LEN)] if (!char.IsDigit(s[i]) || !char.IsDigit(s[i + 1]) || !char.IsDigit(s[i + 2]) || !char.IsDigit(s[i + 3])) yield break; var id = s.Substring(i, 2); if (!int.TryParse(s.AsSpan(i + 2, 2), out var len)) yield break; var end = i + 4 + len; if (len < 0 || end > s.Length) yield break; var val = s.Substring(i + 4, len); yield return val; // ถ้า value ดูเป็น TLV ซ้อนอยู่ ให้ไล่ต่อ if (LooksLikeTlv(val)) foreach (var inner in EnumerateTlvValues(val)) yield return inner; // CRC tag "63" ไม่เกี่ยว แต่เราไม่ถึงเงื่อนไข length อยู่แล้ว i = end; } } private static bool LooksLikeTlv(string s) { int i = 0; while (i + 4 <= s.Length) { if (!char.IsDigit(s[i]) || !char.IsDigit(s[i + 1]) || !char.IsDigit(s[i + 2]) || !char.IsDigit(s[i + 3])) return false; if (!int.TryParse(s.AsSpan(i + 2, 2), out var len)) return false; i += 4 + len; if (i == s.Length) return true; if (i > s.Length) return false; } return false; } private static bool IsAlphaNum(char c) => (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }