feat: Implement Bank Mellat and Daya payment gateway services with initiation, verification, and payout processing
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Real Implementation برای درگاه پرداخت بانک ملت (IPG)
|
||||
/// بانک ملت از SOAP Web Service استفاده میکند
|
||||
/// برای فعالسازی: باید TerminalId, Username, Password را در appsettings.json تنظیم کنید
|
||||
/// </summary>
|
||||
public class BankMellatPaymentService : IPaymentGatewayService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<BankMellatPaymentService> _logger;
|
||||
private readonly string _terminalId;
|
||||
private readonly string _username;
|
||||
private readonly string _password;
|
||||
private readonly string _serviceUrl;
|
||||
|
||||
public BankMellatPaymentService(
|
||||
HttpClient httpClient,
|
||||
IConfiguration configuration,
|
||||
ILogger<BankMellatPaymentService> 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<PaymentInitiateResult> 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 = $@"
|
||||
<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/""
|
||||
xmlns:ns=""http://interfaces.core.sw.bps.com/"">
|
||||
<soap:Body>
|
||||
<ns:bpPayRequest>
|
||||
<terminalId>{_terminalId}</terminalId>
|
||||
<userName>{_username}</userName>
|
||||
<userPassword>{_password}</userPassword>
|
||||
<orderId>{orderId}</orderId>
|
||||
<amount>{amountInRials}</amount>
|
||||
<localDate>{localDate}</localDate>
|
||||
<localTime>{localTime}</localTime>
|
||||
<additionalData>{request.Description}</additionalData>
|
||||
<callBackUrl>{request.CallbackUrl}</callBackUrl>
|
||||
<payerId>0</payerId>
|
||||
</ns:bpPayRequest>
|
||||
</soap:Body>
|
||||
</soap:Envelope>";
|
||||
|
||||
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<PaymentVerificationResult> VerifyPaymentAsync(
|
||||
string refId,
|
||||
string verificationToken,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Verifying Bank Mellat payment: RefId={RefId}", refId);
|
||||
|
||||
// ساخت SOAP Request برای Verify
|
||||
var soapRequest = $@"
|
||||
<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/""
|
||||
xmlns:ns=""http://interfaces.core.sw.bps.com/"">
|
||||
<soap:Body>
|
||||
<ns:bpVerifyRequest>
|
||||
<terminalId>{_terminalId}</terminalId>
|
||||
<userName>{_username}</userName>
|
||||
<userPassword>{_password}</userPassword>
|
||||
<orderId>{verificationToken}</orderId>
|
||||
<saleOrderId>{verificationToken}</saleOrderId>
|
||||
<saleReferenceId>{refId}</saleReferenceId>
|
||||
</ns:bpVerifyRequest>
|
||||
</soap:Body>
|
||||
</soap:Envelope>";
|
||||
|
||||
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 = $@"
|
||||
<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/""
|
||||
xmlns:ns=""http://interfaces.core.sw.bps.com/"">
|
||||
<soap:Body>
|
||||
<ns:bpSettleRequest>
|
||||
<terminalId>{_terminalId}</terminalId>
|
||||
<userName>{_username}</userName>
|
||||
<userPassword>{_password}</userPassword>
|
||||
<orderId>{orderId}</orderId>
|
||||
<saleOrderId>{orderId}</saleOrderId>
|
||||
<saleReferenceId>{refId}</saleReferenceId>
|
||||
</ns:bpSettleRequest>
|
||||
</soap:Body>
|
||||
</soap:Envelope>";
|
||||
|
||||
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<PayoutResult> 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}"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Real Implementation برای درگاه پرداخت دایا
|
||||
/// برای فعالسازی: باید URL و API Key را در appsettings.json تنظیم کنید
|
||||
/// </summary>
|
||||
public class DayaPaymentService : IPaymentGatewayService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<DayaPaymentService> _logger;
|
||||
private readonly string _apiKey;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public DayaPaymentService(
|
||||
HttpClient httpClient,
|
||||
IConfiguration configuration,
|
||||
ILogger<DayaPaymentService> 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<PaymentInitiateResult> 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<DayaInitiateResponse>(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<PaymentVerificationResult> 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<DayaVerifyResponse>(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<PayoutResult> 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<DayaPayoutResponse>(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Services.Payment;
|
||||
|
||||
/// <summary>
|
||||
/// Mock Implementation برای شبیهسازی درگاه پرداخت
|
||||
/// این سرویس فقط برای تست و توسعه است
|
||||
/// </summary>
|
||||
public class MockPaymentGatewayService : IPaymentGatewayService
|
||||
{
|
||||
private readonly ILogger<MockPaymentGatewayService> _logger;
|
||||
|
||||
public MockPaymentGatewayService(ILogger<MockPaymentGatewayService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<PaymentInitiateResult> 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<PaymentVerificationResult> 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<PayoutResult> 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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user