add: implement payment callback handling and enhance financial invoice structure

This commit is contained in:
2025-12-31 15:49:44 +03:30
parent 14ff0a2e59
commit 66a6c411d6
9 changed files with 346 additions and 378 deletions

View File

@@ -0,0 +1,233 @@
using _0_Framework.Application;
using Company.Domain.PaymentTransactionAgg;
using CompanyManagment.App.Contracts.FinancialInvoice;
using CompanyManagment.App.Contracts.FinancialStatment;
using CompanyManagment.App.Contracts.InstitutionContract;
using CompanyManagment.App.Contracts.PaymentCallback;
using CompanyManagment.App.Contracts.PaymentTransaction;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using _0_Framework.Application.PaymentGateway;
using Company.Domain.FinancialStatmentAgg;
using Company.Domain.FinancialTransactionAgg;
using Company.Domain.InstitutionContractAgg;
using CompanyManagment.EFCore.Migrations;
namespace CompanyManagment.Application;
public class PaymentCallbackHandler : IPaymentCallbackHandler
{
private readonly IPaymentTransactionApplication _paymentTransactionApplication;
private readonly IFinancialStatmentApplication _financialStatmentApplication;
private readonly IFinancialStatmentRepository _financialStatmentRepository;
private readonly IFinancialInvoiceApplication _financialInvoiceApplication;
private readonly IInstitutionContractApplication _institutionContractApplication;
private readonly IInstitutionContractRepository _institutionContractRepository;
private readonly IPaymentGateway _paymentGateway;
public PaymentCallbackHandler(
IPaymentTransactionApplication paymentTransactionApplication,
IFinancialStatmentApplication financialStatmentApplication,
IFinancialInvoiceApplication financialInvoiceApplication,
IInstitutionContractApplication institutionContractApplication,
IHttpClientFactory httpClientFactory, IInstitutionContractRepository institutionContractRepository,
IFinancialStatmentRepository financialStatmentRepository)
{
_paymentTransactionApplication = paymentTransactionApplication;
_financialStatmentApplication = financialStatmentApplication;
_financialInvoiceApplication = financialInvoiceApplication;
_institutionContractApplication = institutionContractApplication;
_institutionContractRepository = institutionContractRepository;
_financialStatmentRepository = financialStatmentRepository;
_paymentGateway = new SepehrPaymentGateway(httpClientFactory);
}
/// <summary>
/// تأیید و پردازش callback درگاه پرداخت سپهر
/// </summary>
public async Task<OperationResult> VerifySepehrPaymentCallback(VerifyPaymentCallbackCommand command,
CancellationToken cancellationToken = default)
{
var operation = new OperationResult();
try
{
await using var transactionScope =await _financialStatmentRepository.BeginTransactionAsync();
// گام 1: دریافت اطلاعات تراکنش
var transaction = await _paymentTransactionApplication.GetDetails(command.InvoiceId);
if (transaction == null)
return operation.Failed("تراکنش مورد نظر یافت نشد");
// گام 2: بررسی وضعیت قبلی تراکنش
if (transaction.Status != PaymentTransactionStatus.Pending)
return operation.Failed("این تراکنش قبلا پرداخت شده است");
// گام 3: بررسی کد پاسخ درگاه
if (command.ResponseCode != 0)
{
var failResult = _paymentTransactionApplication.SetFailed(command.InvoiceId);
return failResult.IsSuccedded
? operation.Failed("تراکنش توسط درگاه رد شد")
: operation.Failed("خطا در به‌روزرسانی وضعیت تراکنش");
}
// گام 4: استخراج اطلاعات فاکتور مالی
var extraData = JsonConvert.DeserializeObject<IDictionary<string, object>>(command.Payload ?? "{}");
extraData.TryGetValue("financialInvoiceId", out var financialInvoiceIdObj);
if (financialInvoiceIdObj == null ||
!long.TryParse(financialInvoiceIdObj.ToString(), out var financialInvoiceId))
return operation.Failed("فاکتور مالی نامعتبر است");
// گام 5: دریافت اطلاعات فاکتور مالی
var financialInvoice = _financialInvoiceApplication.GetDetails(financialInvoiceId);
if (financialInvoice == null)
return operation.Failed("فاکتور مالی نامعتبر است");
if (financialInvoice.Status != FinancialInvoiceStatus.Unpaid)
return operation.Failed("فاکتور مالی نامعتبر است");
// گام 6: بررسی تطابق مبلغ
if ((long)financialInvoice.Amount != command.Amount)
{
var failResult = _paymentTransactionApplication.SetFailed(command.InvoiceId);
return operation.Failed("مبلغ تراکنش با مبلغ فاکتور مطابقت ندارد");
}
// گام 7: به‌روزرسانی فاکتور مالی
var setPaidResult = _financialInvoiceApplication.SetPaid(financialInvoiceId, DateTime.Now);
if (!setPaidResult.IsSuccedded)
{
var failResult = _paymentTransactionApplication.SetFailed(command.InvoiceId);
return operation.Failed("خطا در به‌روزرسانی فاکتور مالی");
}
// گام 8: ایجاد سند مالی (Financial Statement)
var createCreditStatementCommand = new CreateFinancialStatment()
{
ContractingPartyId = transaction.ContractingPartyId,
Deptor = 0,
Creditor = command.Amount,
DeptorString = "0",
TypeOfTransaction = "credit",
DescriptionOption = (financialInvoice.Description ?? "") + " شماره فاکتور: " +
(financialInvoice.InvoiceNumber ?? ""),
Description = "درگاه بانکی",
};
var statementResult = _financialStatmentApplication.CreateFromBankGateway(createCreditStatementCommand);
if (!statementResult.IsSuccedded)
{
var failResult = _paymentTransactionApplication.SetFailed(command.InvoiceId);
return operation.Failed("خطا در ایجاد سند مالی");
}
// گام 9: به‌روزرسانی وضعیت تراکنش
var setSuccessResult = _paymentTransactionApplication.SetSuccess(
command.InvoiceId,
command.CardNumber,
command.IssuerBank,
command.Rrn.ToString(),
command.DigitalReceipt);
if (!setSuccessResult.IsSuccedded)
{
return operation.Failed("خطا در به‌روزرسانی وضعیت تراکنش");
}
// گام 10: به‌روزرسانی وضعیت قرارداد‌های نهادی (اگر وجود داشته باشند)
if (financialInvoice.Items?.Any(x =>
x.Type is FinancialInvoiceItemType.BuyInstitutionContract
or FinancialInvoiceItemType.BuyInstitutionContractInstallment) ?? false)
{
await HandleInstitutionContractItems(financialInvoice);
}
// گام 11: تأیید نهایی با درگاه پرداخت
var verifyCommand = new VerifyPaymentGateWayRequest()
{
Amount = transaction.Amount,
TransactionId = command.InvoiceId.ToString(),
DigitalReceipt = command.DigitalReceipt
};
var verifyRes = await _paymentGateway.Verify(verifyCommand, cancellationToken);
#if DEBUG
verifyRes.IsSuccess = true;
#endif
if (!verifyRes.IsSuccess)
{
return operation.Failed("خطا در تایید پرداخت از درگاه");
}
// تمام عملیات موفق - تایید transaction
await transactionScope.CommitAsync(cancellationToken);
return operation.Succcedded();
}
catch (Exception ex)
{
// در صورت بروز هرگونه خطا، transaction خودکار rollback می‌شود
return operation.Failed($"خطا در پردازش callback: {ex.Message}");
}
}
/// <summary>
/// مدیریت آپدیت قرارداد‌های نهادی
/// </summary>
private async Task HandleInstitutionContractItems(EditFinancialInvoice financialInvoice)
{
// قرارداد‌های خریداری مستقیم
var directContractItems = financialInvoice.Items
.Where(x => x.Type == FinancialInvoiceItemType.BuyInstitutionContract);
var financialStatement =
await _financialStatmentRepository.GetByContractingPartyId(financialInvoice.ContractingPartyId);
var today = DateTime.Now;
foreach (var item in directContractItems)
{
var institutionContract = _institutionContractRepository.Get(item.EntityId);
await _institutionContractApplication.SetPendingWorkflow(item.EntityId,
InstitutionContractSigningType.OtpBased);
var financialTransaction = new FinancialTransaction(0, today, today.ToFarsi(),
"پرداخت کل سرویس", "debt", "بابت خدمات", institutionContract.TotalAmount, 0, 0);
financialStatement.AddFinancialTransaction(financialTransaction);
}
// قرارداد‌های خریداری با اقساط
var installmentItems = financialInvoice.Items
.Where(x => x.Type == FinancialInvoiceItemType.BuyInstitutionContractInstallment);
foreach (var item in installmentItems)
{
var institutionContractId =await _institutionContractRepository.GetIdByInstallmentId(item.EntityId);
var institutionContract = _institutionContractRepository.Get(institutionContractId);
await _institutionContractApplication.SetPendingWorkflow(institutionContractId,
InstitutionContractSigningType.OtpBased);
var firstInstallment = institutionContract.Installments.First();
var firstInstallmentAmount = firstInstallment.Amount;
var financialTransaction = new FinancialTransaction(0, today, today.ToFarsi(),
"قسط اول سرویس", "debt", "بابت خدمات", firstInstallmentAmount, 0, 0);
financialStatement.AddFinancialTransaction(financialTransaction);
}
await _financialStatmentRepository.SaveChangesAsync();
}
}