diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/BankMellatPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/BankMellatPaymentService.cs new file mode 100644 index 0000000..e47ae3b --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/BankMellatPaymentService.cs @@ -0,0 +1,367 @@ +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Xml.Linq; + +namespace CMSMicroservice.Infrastructure.Services.Payment; + +/// +/// Real Implementation برای درگاه پرداخت بانک ملت (IPG) +/// بانک ملت از SOAP Web Service استفاده می‌کند +/// برای فعال‌سازی: باید TerminalId, Username, Password را در appsettings.json تنظیم کنید +/// +public class BankMellatPaymentService : IPaymentGatewayService +{ + private readonly HttpClient _httpClient; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly string _terminalId; + private readonly string _username; + private readonly string _password; + private readonly string _serviceUrl; + + public BankMellatPaymentService( + HttpClient httpClient, + IConfiguration configuration, + ILogger logger) + { + _httpClient = httpClient; + _configuration = configuration; + _logger = logger; + + // خواندن تنظیمات از appsettings.json + _terminalId = _configuration["BankMellat:TerminalId"] ?? throw new InvalidOperationException( + "BankMellat:TerminalId is not configured"); + _username = _configuration["BankMellat:Username"] ?? throw new InvalidOperationException( + "BankMellat:Username is not configured"); + _password = _configuration["BankMellat:Password"] ?? throw new InvalidOperationException( + "BankMellat:Password is not configured"); + _serviceUrl = _configuration["BankMellat:ServiceUrl"] ?? "https://bpm.shaparak.ir/pgwchannel/services/pgw"; + + _httpClient.Timeout = TimeSpan.FromSeconds(30); + } + + public async Task InitiatePaymentAsync( + PaymentRequest request, + CancellationToken cancellationToken = default) + { + try + { + _logger.LogInformation( + "Initiating Bank Mellat payment: UserId={UserId}, Amount={Amount}", + request.UserId, request.Amount); + + // تبدیل مبلغ به ریال (بانک ملت ریال می‌خواهد) + var amountInRials = (long)(request.Amount * 10); + var localDate = DateTime.Now.ToString("yyyyMMdd"); + var localTime = DateTime.Now.ToString("HHmmss"); + var orderId = $"{request.UserId}_{DateTime.Now.Ticks}"; + + // ساخت SOAP Request + var soapRequest = $@" + + + + {_terminalId} + {_username} + {_password} + {orderId} + {amountInRials} + {localDate} + {localTime} + {request.Description} + {request.CallbackUrl} + 0 + + + "; + + var content = new StringContent(soapRequest, Encoding.UTF8, "text/xml"); + content.Headers.Add("SOAPAction", "http://interfaces.core.sw.bps.com/IPaymentGateway/bpPayRequest"); + + var response = await _httpClient.PostAsync(_serviceUrl, content, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + _logger.LogError( + "Bank Mellat API error: StatusCode={StatusCode}", + response.StatusCode); + + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطا در ارتباط با بانک ملت: {response.StatusCode}" + }; + } + + var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); + var refId = ParseSoapResponse(responseContent, "return"); + + // بررسی کد خطا + if (string.IsNullOrEmpty(refId) || !long.TryParse(refId, out var refIdNumber)) + { + _logger.LogError("Invalid RefId from Bank Mellat: {RefId}", refId); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = "پاسخ نامعتبر از بانک ملت" + }; + } + + if (refIdNumber < 0) + { + var errorMessage = GetBankMellatErrorMessage(refIdNumber.ToString()); + _logger.LogError("Bank Mellat error code: {ErrorCode} - {Message}", refIdNumber, errorMessage); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = errorMessage + }; + } + + _logger.LogInformation( + "Bank Mellat payment initiated successfully: RefId={RefId}", + refId); + + // URL درگاه بانک ملت + var gatewayUrl = $"https://bpm.shaparak.ir/pgwchannel/startpay.mellat?RefId={refId}"; + + return new PaymentInitiateResult + { + IsSuccess = true, + RefId = refId, + GatewayUrl = gatewayUrl, + ErrorMessage = null + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in InitiatePaymentAsync"); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = "خطای غیرمنتظره در برقراری ارتباط با بانک" + }; + } + } + + public async Task VerifyPaymentAsync( + string refId, + string verificationToken, + CancellationToken cancellationToken = default) + { + try + { + _logger.LogInformation("Verifying Bank Mellat payment: RefId={RefId}", refId); + + // ساخت SOAP Request برای Verify + var soapRequest = $@" + + + + {_terminalId} + {_username} + {_password} + {verificationToken} + {verificationToken} + {refId} + + + "; + + var content = new StringContent(soapRequest, Encoding.UTF8, "text/xml"); + content.Headers.Add("SOAPAction", "http://interfaces.core.sw.bps.com/IPaymentGateway/bpVerifyRequest"); + + var response = await _httpClient.PostAsync(_serviceUrl, content, cancellationToken); + var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); + var result = ParseSoapResponse(responseContent, "return"); + + var isSuccess = result == "0"; // 0 = موفق + + if (isSuccess) + { + // اگر Verify موفق بود، باید Settle کنیم + await SettlePaymentAsync(refId, verificationToken, cancellationToken); + } + + _logger.LogInformation( + "Bank Mellat verification result: RefId={RefId}, IsSuccess={IsSuccess}", + refId, isSuccess); + + return new PaymentVerificationResult + { + IsSuccess = isSuccess, + RefId = refId, + TrackingCode = refId, + Amount = 0, // مبلغ باید از Database بیاید + Message = isSuccess ? "تراکنش موفق" : GetBankMellatErrorMessage(result) + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in VerifyPaymentAsync"); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = "خطا در تأیید پرداخت" + }; + } + } + + private async Task SettlePaymentAsync(string refId, string orderId, CancellationToken cancellationToken) + { + try + { + var soapRequest = $@" + + + + {_terminalId} + {_username} + {_password} + {orderId} + {orderId} + {refId} + + + "; + + var content = new StringContent(soapRequest, Encoding.UTF8, "text/xml"); + content.Headers.Add("SOAPAction", "http://interfaces.core.sw.bps.com/IPaymentGateway/bpSettleRequest"); + + var response = await _httpClient.PostAsync(_serviceUrl, content, cancellationToken); + var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); + var result = ParseSoapResponse(responseContent, "return"); + + var isSuccess = result == "0"; + _logger.LogInformation( + "Bank Mellat settle result: RefId={RefId}, IsSuccess={IsSuccess}", + refId, isSuccess); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in SettlePaymentAsync"); + } + } + + public async Task ProcessPayoutAsync( + PayoutRequest request, + CancellationToken cancellationToken = default) + { + try + { + _logger.LogInformation( + "Processing Bank Mellat payout: UserId={UserId}, Amount={Amount}, IBAN={Iban}", + request.UserId, request.Amount, request.Iban); + + // Validation + if (!request.Iban.StartsWith("IR") || request.Iban.Length != 26) + { + return new PayoutResult + { + IsSuccess = false, + Message = "فرمت شماره شبا نامعتبر است", + ProcessedAt = DateTime.UtcNow + }; + } + + if (request.Amount < 10_000) + { + return new PayoutResult + { + IsSuccess = false, + Message = "حداقل مبلغ برداشت 10,000 تومان است", + ProcessedAt = DateTime.UtcNow + }; + } + + // TODO: بانک ملت ممکن است API واریز مستقیم نداشته باشد + // در این صورت باید از Shaparak Paya (سامانه پایا) استفاده کرد + // یا از سرویس‌های واسط مانند Fanapay, IPG.ir استفاده شود + + _logger.LogWarning( + "Bank Mellat direct payout is not supported. Use Shaparak Paya or third-party service."); + + return new PayoutResult + { + IsSuccess = false, + Message = "واریز مستقیم از طریق بانک ملت پشتیبانی نمی‌شود. از سامانه پایا استفاده کنید.", + ProcessedAt = DateTime.UtcNow + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ProcessPayoutAsync"); + return new PayoutResult + { + IsSuccess = false, + Message = "خطا در پردازش واریز", + ProcessedAt = DateTime.UtcNow + }; + } + } + + // Helper method to parse SOAP XML response + private string ParseSoapResponse(string soapResponse, string elementName) + { + try + { + var doc = XDocument.Parse(soapResponse); + var ns = doc.Root?.GetDefaultNamespace(); + var element = doc.Descendants(ns + elementName).FirstOrDefault(); + return element?.Value ?? string.Empty; + } + catch + { + return string.Empty; + } + } + + // کدهای خطای بانک ملت + private string GetBankMellatErrorMessage(string errorCode) + { + return errorCode switch + { + "0" => "تراکنش موفق", + "11" => "شماره کارت نامعتبر است", + "12" => "موجودی کافی نیست", + "13" => "رمز نادرست است", + "14" => "تعداد دفعات وارد کردن رمز بیش از حد مجاز است", + "15" => "کارت نامعتبر است", + "16" => "دفعات برداشت وجه بیش از حد مجاز است", + "17" => "کاربر از انجام تراکنش منصرف شده است", + "18" => "تاریخ انقضای کارت گذشته است", + "19" => "مبلغ برداشت وجه بیش از حد مجاز است", + "21" => "پذیرنده نامعتبر است", + "23" => "خطای امنیتی رخ داده است", + "24" => "اطلاعات کاربری پذیرنده نامعتبر است", + "25" => "مبلغ نامعتبر است", + "31" => "پاسخ نامعتبر است", + "32" => "فرمت اطلاعات وارد شده صحیح نمی‌باشد", + "33" => "حساب نامعتبر است", + "34" => "خطای سیستمی", + "35" => "تاریخ نامعتبر است", + "41" => "شماره درخواست تکراری است", + "42" => "تراکنش یافت نشد", + "43" => "قبلا درخواست Verify داده شده است", + "44" => "درخواست Verify یافت نشد", + "45" => "تراکنش Settle شده است", + "46" => "تراکنش Settle نشده است", + "47" => "تراکنش Settle یافت نشد", + "48" => "تراکنش Reverse شده است", + "49" => "تراکنش Refund یافت نشد", + "51" => "تراکنش تکراری است", + "54" => "تراکنش مرجع موجود نیست", + "55" => "تراکنش نامعتبر است", + "61" => "خطا در واریز", + _ => $"خطای ناشناخته: {errorCode}" + }; + } +} diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs new file mode 100644 index 0000000..b6bb4a4 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs @@ -0,0 +1,318 @@ +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; + +namespace CMSMicroservice.Infrastructure.Services.Payment; + +/// +/// Real Implementation برای درگاه پرداخت دایا +/// برای فعال‌سازی: باید URL و API Key را در appsettings.json تنظیم کنید +/// +public class DayaPaymentService : IPaymentGatewayService +{ + private readonly HttpClient _httpClient; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly string _apiKey; + private readonly string _baseUrl; + + public DayaPaymentService( + HttpClient httpClient, + IConfiguration configuration, + ILogger logger) + { + _httpClient = httpClient; + _configuration = configuration; + _logger = logger; + + // خواندن تنظیمات از appsettings.json + _baseUrl = _configuration["DayaPayment:BaseUrl"] ?? "https://api.daya.ir"; + _apiKey = _configuration["DayaPayment:ApiKey"] ?? throw new InvalidOperationException( + "DayaPayment:ApiKey is not configured in appsettings.json"); + + _httpClient.BaseAddress = new Uri(_baseUrl); + _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}"); + _httpClient.Timeout = TimeSpan.FromSeconds(30); + } + + public async Task InitiatePaymentAsync( + PaymentRequest request, + CancellationToken cancellationToken = default) + { + try + { + _logger.LogInformation( + "Initiating Daya payment: UserId={UserId}, Amount={Amount}", + request.UserId, request.Amount); + + // ساختار Request برای API دایا + var apiRequest = new + { + amount = request.Amount, + mobile = request.Mobile, + description = request.Description, + callback_url = request.CallbackUrl, + user_id = request.UserId + }; + + var response = await _httpClient.PostAsJsonAsync( + "/api/v1/payment/initiate", + apiRequest, + cancellationToken); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); + _logger.LogError( + "Daya API error: StatusCode={StatusCode}, Error={Error}", + response.StatusCode, errorContent); + + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطا در ارتباط با درگاه: {response.StatusCode}" + }; + } + + var result = await response.Content.ReadFromJsonAsync(cancellationToken); + + if (result == null || string.IsNullOrEmpty(result.RefId)) + { + _logger.LogError("Invalid response from Daya API"); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = "پاسخ نامعتبر از درگاه" + }; + } + + _logger.LogInformation( + "Daya payment initiated successfully: RefId={RefId}", + result.RefId); + + return new PaymentInitiateResult + { + IsSuccess = true, + RefId = result.RefId, + GatewayUrl = result.GatewayUrl, + ErrorMessage = null + }; + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Network error while calling Daya API"); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = "خطا در ارتباط با سرور درگاه" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error in InitiatePaymentAsync"); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = "خطای غیرمنتظره" + }; + } + } + + public async Task VerifyPaymentAsync( + string refId, + string verificationToken, + CancellationToken cancellationToken = default) + { + try + { + _logger.LogInformation("Verifying Daya payment: RefId={RefId}", refId); + + var apiRequest = new + { + ref_id = refId, + token = verificationToken + }; + + var response = await _httpClient.PostAsJsonAsync( + "/api/v1/payment/verify", + apiRequest, + cancellationToken); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); + _logger.LogError( + "Daya verification error: StatusCode={StatusCode}, Error={Error}", + response.StatusCode, errorContent); + + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = $"خطا در تأیید پرداخت: {response.StatusCode}" + }; + } + + var result = await response.Content.ReadFromJsonAsync(cancellationToken); + + if (result == null) + { + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = "پاسخ نامعتبر از درگاه" + }; + } + + _logger.LogInformation( + "Daya payment verified: RefId={RefId}, IsSuccess={IsSuccess}, TrackingCode={TrackingCode}", + refId, result.IsSuccess, result.TrackingCode); + + return new PaymentVerificationResult + { + IsSuccess = result.IsSuccess, + RefId = refId, + TrackingCode = result.TrackingCode, + Amount = result.Amount, + Message = result.Message + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in VerifyPaymentAsync"); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = "خطا در تأیید پرداخت" + }; + } + } + + public async Task ProcessPayoutAsync( + PayoutRequest request, + CancellationToken cancellationToken = default) + { + try + { + _logger.LogInformation( + "Processing Daya payout: UserId={UserId}, Amount={Amount}, IBAN={Iban}", + request.UserId, request.Amount, request.Iban); + + // Validation + if (!request.Iban.StartsWith("IR") || request.Iban.Length != 26) + { + _logger.LogWarning("Invalid IBAN format: {Iban}", request.Iban); + return new PayoutResult + { + IsSuccess = false, + Message = "فرمت شماره شبا نامعتبر است", + ProcessedAt = DateTime.UtcNow + }; + } + + if (request.Amount < 10_000) + { + _logger.LogWarning("Amount too low: {Amount}", request.Amount); + return new PayoutResult + { + IsSuccess = false, + Message = "حداقل مبلغ برداشت 10,000 تومان است", + ProcessedAt = DateTime.UtcNow + }; + } + + var apiRequest = new + { + amount = request.Amount, + iban = request.Iban, + account_holder_name = request.AccountHolderName, + description = request.Description, + internal_ref_id = request.InternalRefId, + user_id = request.UserId + }; + + var response = await _httpClient.PostAsJsonAsync( + "/api/v1/payout/process", + apiRequest, + cancellationToken); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); + _logger.LogError( + "Daya payout error: StatusCode={StatusCode}, Error={Error}", + response.StatusCode, errorContent); + + return new PayoutResult + { + IsSuccess = false, + Message = $"خطا در واریز: {response.StatusCode}", + ProcessedAt = DateTime.UtcNow + }; + } + + var result = await response.Content.ReadFromJsonAsync(cancellationToken); + + if (result == null) + { + return new PayoutResult + { + IsSuccess = false, + Message = "پاسخ نامعتبر از درگاه", + ProcessedAt = DateTime.UtcNow + }; + } + + _logger.LogInformation( + "Daya payout processed: IsSuccess={IsSuccess}, BankRefId={BankRefId}", + result.IsSuccess, result.BankRefId); + + return new PayoutResult + { + IsSuccess = result.IsSuccess, + BankRefId = result.BankRefId, + TrackingCode = result.TrackingCode, + Message = result.Message, + ProcessedAt = DateTime.UtcNow + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ProcessPayoutAsync"); + return new PayoutResult + { + IsSuccess = false, + Message = "خطا در پردازش واریز", + ProcessedAt = DateTime.UtcNow + }; + } + } + + // DTO classes for Daya API + private class DayaInitiateResponse + { + public string RefId { get; set; } = string.Empty; + public string GatewayUrl { get; set; } = string.Empty; + } + + private class DayaVerifyResponse + { + public bool IsSuccess { get; set; } + public string TrackingCode { get; set; } = string.Empty; + public decimal Amount { get; set; } + public string Message { get; set; } = string.Empty; + } + + private class DayaPayoutResponse + { + public bool IsSuccess { get; set; } + public string BankRefId { get; set; } = string.Empty; + public string TrackingCode { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + } +} diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs new file mode 100644 index 0000000..88c0eee --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs @@ -0,0 +1,127 @@ +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Infrastructure.Services.Payment; + +/// +/// Mock Implementation برای شبیه‌سازی درگاه پرداخت +/// این سرویس فقط برای تست و توسعه است +/// +public class MockPaymentGatewayService : IPaymentGatewayService +{ + private readonly ILogger _logger; + + public MockPaymentGatewayService(ILogger logger) + { + _logger = logger; + } + + public async Task InitiatePaymentAsync( + PaymentRequest request, + CancellationToken cancellationToken = default) + { + _logger.LogWarning("⚠️ Using MOCK Payment Gateway - Replace with real implementation in production!"); + + // شبیه‌سازی تاخیر شبکه + await Task.Delay(200, cancellationToken); + + // شبیه‌سازی RefId + var refId = $"MOCK-PAY-{DateTime.Now.Ticks}"; + + _logger.LogInformation("Mock payment initiated: RefId={RefId}, Amount={Amount}, User={UserId}", + refId, request.Amount, request.UserId); + + return new PaymentInitiateResult + { + IsSuccess = true, + RefId = refId, + GatewayUrl = $"https://mock-gateway.local/pay?ref={refId}", + ErrorMessage = null + }; + } + + public async Task VerifyPaymentAsync( + string refId, + string verificationToken, + CancellationToken cancellationToken = default) + { + _logger.LogWarning("⚠️ Using MOCK Payment Gateway - Verification"); + + // شبیه‌سازی تاخیر شبکه + await Task.Delay(150, cancellationToken); + + // شبیه‌سازی: همه تراکنش‌ها موفق هستند + var isSuccess = true; + var trackingCode = $"TRK-{DateTime.Now.Ticks}"; + + if (isSuccess) + { + _logger.LogInformation("Mock payment verified successfully: RefId={RefId}, Tracking={TrackingCode}", + refId, trackingCode); + } + else + { + _logger.LogWarning("Mock payment verification failed: RefId={RefId}", refId); + } + + return new PaymentVerificationResult + { + IsSuccess = isSuccess, + RefId = refId, + TrackingCode = trackingCode, + Amount = 0, // باید از Database بیاید + Message = isSuccess ? "تراکنش موفق (Mock)" : "تراکنش ناموفق (Mock)" + }; + } + + public async Task ProcessPayoutAsync( + PayoutRequest request, + CancellationToken cancellationToken = default) + { + _logger.LogWarning("⚠️ Using MOCK Payment Gateway - Payout"); + + // شبیه‌سازی تاخیر شبکه + await Task.Delay(300, cancellationToken); + + // Validation: چک کردن شبا (باید IR بخوره و 26 کاراکتر باشد) + if (!request.Iban.StartsWith("IR") || request.Iban.Length != 26) + { + _logger.LogError("Invalid IBAN format: {Iban}", request.Iban); + return new PayoutResult + { + IsSuccess = false, + Message = "فرمت شماره شبا نامعتبر است", + ProcessedAt = DateTime.UtcNow + }; + } + + // Validation: چک کردن مبلغ (حداقل 10,000 تومان) + if (request.Amount < 10_000) + { + _logger.LogError("Payout amount too low: {Amount}", request.Amount); + return new PayoutResult + { + IsSuccess = false, + Message = "حداقل مبلغ برداشت 10,000 تومان است", + ProcessedAt = DateTime.UtcNow + }; + } + + // شبیه‌سازی: همه واریزها موفق هستند + var bankRefId = $"BANK-{DateTime.Now.Ticks}"; + var trackingCode = $"TRK-PAYOUT-{DateTime.Now.Ticks}"; + + _logger.LogInformation( + "Mock payout processed successfully: User={UserId}, Amount={Amount}, IBAN={Iban}, BankRef={BankRefId}", + request.UserId, request.Amount, request.Iban, bankRefId); + + return new PayoutResult + { + IsSuccess = true, + BankRefId = bankRefId, + TrackingCode = trackingCode, + Message = $"واریز {request.Amount:N0} تومان به حساب {request.Iban} با موفقیت انجام شد (Mock)", + ProcessedAt = DateTime.UtcNow + }; + } +}