feat(payment): add GetUnverifiedAuthoritiesAsync method to IPaymentGatewayService and ZarinPalPaymentService for payment reconciliation
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 8m7s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 8m7s
- Introduced GetUnverifiedAuthoritiesAsync method to retrieve authorities with unverified payments. - Implemented ZarinPal-specific logic for fetching unverified authorities from the new endpoint. - Added background job for Zarinpal payment reconciliation every 30 minutes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -30,6 +30,7 @@ public class ZarinPalPaymentService : IPaymentGatewayService
|
||||
// مسیرهای API (مشترک)
|
||||
private const string RequestEndpoint = "/pg/v4/payment/request.json";
|
||||
private const string VerifyEndpoint = "/pg/v4/payment/verify.json";
|
||||
private const string UnverifiedEndpoint = "/pg/v4/payment/unVerified.json";
|
||||
private const string StartPayPath = "/pg/StartPay/";
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
@@ -271,6 +272,49 @@ public class ZarinPalPaymentService : IPaymentGatewayService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// فهرست Authorityهایی که کاربر پرداخت کرده ولی Verify نشدهاند
|
||||
/// زرینپال endpoint: POST /pg/v4/payment/unVerified.json
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<string>> GetUnverifiedAuthoritiesAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestBody = new ZarinPalUnverifiedRequest { MerchantId = _merchantId };
|
||||
var json = JsonSerializer.Serialize(requestBody, JsonOptions);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync(UnverifiedEndpoint, content, cancellationToken);
|
||||
var body = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
_logger.LogDebug("ZarinPal unVerified response: {StatusCode} - {Body}", response.StatusCode, body);
|
||||
|
||||
var result = JsonSerializer.Deserialize<ZarinPalUnverifiedResponse>(body, JsonOptions);
|
||||
|
||||
if (result?.Data?.Code == 100 && result.Data.Authorities?.Count > 0)
|
||||
{
|
||||
var authorities = result.Data.Authorities
|
||||
.Where(a => !string.IsNullOrEmpty(a.Authority))
|
||||
.Select(a => a.Authority!)
|
||||
.ToList();
|
||||
|
||||
_logger.LogInformation(
|
||||
"ZarinPal unVerified: {Count} unverified payment(s) found", authorities.Count);
|
||||
|
||||
return authorities;
|
||||
}
|
||||
|
||||
_logger.LogDebug("ZarinPal unVerified: no pending payments. Code={Code}", result?.Data?.Code);
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "ZarinPal GetUnverifiedAuthorities failed — will fallback to DB scan");
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// زرینپال Payout مستقیم ندارد — این متد NotSupported برمیگرداند
|
||||
/// برای Payout باید از سرویس دیگری (مثل دایا) استفاده شود
|
||||
@@ -339,6 +383,31 @@ public class ZarinPalPaymentService : IPaymentGatewayService
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
private class ZarinPalUnverifiedRequest
|
||||
{
|
||||
public string MerchantId { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private class ZarinPalUnverifiedResponse
|
||||
{
|
||||
public ZarinPalUnverifiedData? Data { get; set; }
|
||||
}
|
||||
|
||||
private class ZarinPalUnverifiedData
|
||||
{
|
||||
public int? Code { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public List<ZarinPalUnverifiedAuthority>? Authorities { get; set; }
|
||||
}
|
||||
|
||||
private class ZarinPalUnverifiedAuthority
|
||||
{
|
||||
public string? Authority { get; set; }
|
||||
public long? Amount { get; set; }
|
||||
public string? Channel { get; set; }
|
||||
public string? Date { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ZarinPal returns errors as [] (empty array) when no error, or as {...} object when there's an error.
|
||||
/// This converter handles both cases.
|
||||
|
||||
Reference in New Issue
Block a user