Fix Redundance Reference
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
using AMREZ.EOP.Abstractions.Applications.UseCases.Payments.SlipVerification.BankDetect;
|
||||
using AMREZ.EOP.Application.UseCases.Payments.SlipVerification.QrDecode;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace AMREZ.EOP.Application.UseCases.Payments.SlipVerification.BankDetect;
|
||||
|
||||
public static class BankDetect
|
||||
public static class ParserBankDetect
|
||||
{
|
||||
// SCB=014, KBank=004
|
||||
public static string? FromQrText(string? raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return null;
|
||||
|
||||
@@ -1,6 +1,87 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AMREZ.EOP.Application.UseCases.Payments.SlipVerification.QrDecode;
|
||||
|
||||
public class TransRefParser
|
||||
public static partial class TransRefParser
|
||||
{
|
||||
|
||||
[GeneratedRegex(@"(?i)(?:[?&](?:transref|transactionid|txnid|transid)=)(?<id>[-A-Za-z0-9]{10,64})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex QueryId();
|
||||
|
||||
[GeneratedRegex(@"(?<![A-Za-z0-9])(?<id>[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<string> 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');
|
||||
}
|
||||
@@ -1,6 +1,22 @@
|
||||
using AMREZ.EOP.Abstractions.Applications.UseCases.Payments.SlipVerification.QrDecode;
|
||||
using SkiaSharp;
|
||||
using ZXing.SkiaSharp;
|
||||
|
||||
namespace AMREZ.EOP.Application.UseCases.Payments.SlipVerification.QrDecode;
|
||||
|
||||
public class ZxingQrDecoder
|
||||
public sealed class ZxingQrDecoder : IQrDecoder
|
||||
{
|
||||
|
||||
public string? TryDecodeText(byte[] imageBytes)
|
||||
{
|
||||
using var bmp = SKBitmap.Decode(imageBytes);
|
||||
if (bmp is null) return null;
|
||||
|
||||
var reader = new BarcodeReader
|
||||
{
|
||||
AutoRotate = true,
|
||||
Options = { TryHarder = true, PossibleFormats = new[] { ZXing.BarcodeFormat.QR_CODE } }
|
||||
};
|
||||
var res = reader.Decode(bmp);
|
||||
return res?.Text;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,93 @@
|
||||
using System.Globalization;
|
||||
using AMREZ.EOP.Abstractions.Applications.UseCases.Payments.SlipVerification;
|
||||
using AMREZ.EOP.Abstractions.Applications.UseCases.Payments.SlipVerification.QrDecode;
|
||||
using AMREZ.EOP.Abstractions.Infrastructures.Integrations.SCB.Clients;
|
||||
using AMREZ.EOP.Application.UseCases.Payments.SlipVerification.BankDetect;
|
||||
using AMREZ.EOP.Application.UseCases.Payments.SlipVerification.QrDecode;
|
||||
using AMREZ.EOP.Contracts.DTOs.Payments.SlipVerification;
|
||||
using AMREZ.EOP.Domain.Shared.Payments;
|
||||
|
||||
namespace AMREZ.EOP.Application.UseCases.Payments.SlipVerification;
|
||||
|
||||
public class VerifyFromImageUseCase
|
||||
public sealed class VerifyFromImageUseCase : IVerifyFromImageUseCase
|
||||
{
|
||||
|
||||
private readonly IQrDecoder _qr;
|
||||
private readonly IScbSlipClient _scb;
|
||||
private readonly SCBOptions _opts;
|
||||
|
||||
public VerifyFromImageUseCase(IQrDecoder qr, IScbSlipClient scb, SCBOptions opts)
|
||||
{
|
||||
_qr = qr;
|
||||
_scb = scb;
|
||||
_opts = opts;
|
||||
}
|
||||
|
||||
public async Task<VerifyFromImageResponse?> ExecuteAsync(VerifyFromImageRequest request, CancellationToken ct)
|
||||
{
|
||||
await using var ms = new MemoryStream();
|
||||
await request.Image.CopyToAsync(ms, ct);
|
||||
|
||||
var qrText = _qr.TryDecodeText(ms.ToArray());
|
||||
var transId = TransRefParser.TryExtract(qrText);
|
||||
var bank = ParserBankDetect.FromQrText(qrText) ?? _opts.DefaultSendingBank;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(transId)) return null; // ไม่มี id ก็หยุด
|
||||
|
||||
// ← SCB API ต้องการ “id” (transRef/txnId) + sendingBank เท่านั้น
|
||||
var env = await _scb.VerifyAsync(transId, bank, ct);
|
||||
if (env?.Status?.Code != 1000 || env.Data is null) return null;
|
||||
|
||||
// 3) map → controller response
|
||||
var d = env.Data;
|
||||
var amount = decimal.TryParse(d.PaidLocalAmount ?? d.Amount ?? "0", NumberStyles.Any,
|
||||
CultureInfo.InvariantCulture, out var v)
|
||||
? v
|
||||
: 0m;
|
||||
var when = CombineBangkok(d.TransDate, d.TransTime);
|
||||
|
||||
var msisdn = d.Sender?.Proxy?.Type == "MSISDN" ? d.Sender.Proxy.Value : null;
|
||||
var biller = d.Receiver?.Proxy?.Type == "BILLERID" ? d.Receiver.Proxy.Value : null;
|
||||
|
||||
return new VerifyFromImageResponse
|
||||
{
|
||||
TransRef = d.TransRef ?? transId,
|
||||
SendingBank = bank,
|
||||
AmountTHB = amount,
|
||||
CurrencyNumeric = d.PaidLocalCurrency ?? "764",
|
||||
SenderMsisdn = msisdn,
|
||||
BillerId = biller,
|
||||
TransactedAt = when
|
||||
};
|
||||
}
|
||||
|
||||
private static DateTimeOffset CombineBangkok(string? date, string? time)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(date) || string.IsNullOrWhiteSpace(time))
|
||||
return DateTimeOffset.UtcNow; // fallback
|
||||
|
||||
var dt = DateTime.ParseExact($"{date} {time}", "yyyyMMdd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
var local = DateTime.SpecifyKind(dt, DateTimeKind.Unspecified);
|
||||
|
||||
TimeZoneInfo? tz = null;
|
||||
try
|
||||
{
|
||||
tz = TimeZoneInfo.FindSystemTimeZoneById("Asia/Bangkok");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (tz is null)
|
||||
try
|
||||
{
|
||||
tz = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return tz is null
|
||||
? new DateTimeOffset(local, TimeSpan.FromHours(7))
|
||||
: new DateTimeOffset(local, tz.GetUtcOffset(local));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user