feat: add PaymentTransaction table for gateway-level tracking
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 9m6s

- New PaymentTransaction entity (Domain/Entities/Payment/) with all gateway fields:
  GatewayProvider, MerchantId, Authority, CardPan, CardHash, RefId, VerificationStatusCode, etc.
- New PaymentTransactionConfiguration with indexes on Authority, GatewayProvider, UserId, TransactionId, RefId
- Added DbSet<PaymentTransaction> to IApplicationDbContext and ApplicationDbContext
- Extended PaymentVerificationResult DTO with CardPan, CardHash, VerificationCode
- Updated ZarinPalPaymentService.VerifyPayment to return CardPan/CardHash/VerificationCode
- Updated all 5 payment consumers to create/update PaymentTransaction:
  * PlaceOrderCommandHandler — creates PaymentTransaction after InitiatePayment
  * PaymentCallbackController — updates PaymentTransaction after VerifyPayment
  * ChargeDiscountWalletCommandHandler — creates PaymentTransaction + fixed callback URL
  * VerifyDiscountWalletChargeCommandHandler — updates PaymentTransaction after verify
  * TransactionsService.CustomerPaymentRequest/Verification — create/update PaymentTransaction
  * PackageService.CustomerPurchasePackage/Verify — create/update PaymentTransaction
- Transaction table untouched — PaymentTransaction is a separate table
- Pattern inspired by PYMS: create row before gateway → update after verify
- EF migration: AddPaymentTransactionTable
This commit is contained in:
masoodafar-web
2026-02-15 23:53:28 +03:30
parent 207f53ef80
commit a39a36e66d
15 changed files with 5017 additions and 5 deletions
@@ -34,6 +34,7 @@ public interface IApplicationDbContext
DbSet<UserWallet> UserWallets { get; }
DbSet<UserWalletChangeLog> UserWalletChangeLogs { get; }
DbSet<ManualPayment> ManualPayments { get; }
DbSet<PaymentTransaction> PaymentTransactions { get; }
DbSet<PublicMessage> PublicMessages { get; }
DbSet<ClubMembership> ClubMemberships { get; }
DbSet<ClubMembershipHistory> ClubMembershipHistories { get; }
@@ -142,6 +142,21 @@ public class PaymentVerificationResult
/// پیام
/// </summary>
public string? Message { get; set; }
/// <summary>
/// شماره کارت ماسک‌شده (مثلاً 6037-****-****-1234)
/// </summary>
public string? CardPan { get; set; }
/// <summary>
/// هش کارت بانکی
/// </summary>
public string? CardHash { get; set; }
/// <summary>
/// کد وضعیت verify از درگاه (100=موفق، 101=تکراری)
/// </summary>
public int? VerificationCode { get; set; }
}
/// <summary>
@@ -207,6 +207,25 @@ public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, Place
{
// ذخیره Authority/RefId در تراکنش برای verify بعدی
transaction.RefId = paymentResult.RefId;
// ثبت PaymentTransaction — جدول جدید با اطلاعات درگاه
var paymentTx = new PaymentTransaction
{
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
Amount = finalGatewayAmount,
CallbackUrl = callbackUrl,
Description = $"فروشگاه تخفیفی - سفارش #{order.Id}",
Mobile = null,
UserId = request.UserId,
RequestStatusCode = 100,
RequestStatusMessage = "Success",
Authority = paymentResult.RefId,
PaymentStatus = false, // هنوز verify نشده
TransactionId = transaction.Id,
OrderId = order.Id.ToString()
};
_context.PaymentTransactions.Add(paymentTx);
await _context.SaveChangesAsync(cancellationToken);
paymentUrl = paymentResult.GatewayUrl;
@@ -2,8 +2,10 @@ using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Entities.Payment;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet;
@@ -13,15 +15,18 @@ public class ChargeDiscountWalletCommandHandler
{
private readonly IApplicationDbContext _context;
private readonly IPaymentGatewayService _paymentGateway;
private readonly IConfiguration _configuration;
private readonly ILogger<ChargeDiscountWalletCommandHandler> _logger;
public ChargeDiscountWalletCommandHandler(
IApplicationDbContext context,
IPaymentGatewayService paymentGateway,
IConfiguration configuration,
ILogger<ChargeDiscountWalletCommandHandler> logger)
{
_context = context;
_paymentGateway = paymentGateway;
_configuration = configuration;
_logger = logger;
}
@@ -58,12 +63,15 @@ public class ChargeDiscountWalletCommandHandler
}
// 3. ایجاد درخواست پرداخت
var cmsBaseUrl = _configuration["CmsBaseUrl"] ?? "https://localhost:32846";
var callbackUrl = $"{cmsBaseUrl}/api/wallet/verify-discount-charge";
var paymentRequest = new PaymentRequest
{
Amount = request.Amount,
UserId = user.Id,
Mobile = user.Mobile ?? "",
CallbackUrl = $"https://yourdomain.com/api/wallet/verify-discount-charge",
CallbackUrl = callbackUrl,
Description = $"شارژ کیف پول تخفیفی - کاربر {user.Id}"
};
@@ -80,10 +88,29 @@ public class ChargeDiscountWalletCommandHandler
throw new Exception($"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}");
}
// 4. ثبت PaymentTransaction
var paymentTx = new PaymentTransaction
{
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
Amount = request.Amount,
CallbackUrl = callbackUrl,
Description = $"شارژ کیف پول تخفیفی - کاربر {user.Id}",
Mobile = user.Mobile,
UserId = user.Id,
RequestStatusCode = 100,
RequestStatusMessage = "Success",
Authority = paymentResult.RefId,
PaymentStatus = false
};
_context.PaymentTransactions.Add(paymentTx);
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Discount wallet charge initiated. UserId: {UserId}, RefId: {RefId}",
"Discount wallet charge initiated. UserId: {UserId}, RefId: {RefId}, PaymentTxId: {PaymentTxId}",
user.Id,
paymentResult.RefId
paymentResult.RefId,
paymentTx.Id
);
return paymentResult;
@@ -56,6 +56,19 @@ public class VerifyDiscountWalletChargeCommandHandler
"OK" // وقتی این handler فراخوانی میشه یعنی کاربر از درگاه برگشته — Status باید OK باشه
);
// آپدیت PaymentTransaction با نتیجه verify
var paymentTx = await _context.PaymentTransactions
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken);
if (paymentTx != null)
{
paymentTx.PaymentStatus = verifyResult.IsSuccess;
paymentTx.VerificationStatusCode = verifyResult.VerificationCode;
paymentTx.VerificationStatusMessage = verifyResult.Message;
paymentTx.CardPan = verifyResult.CardPan;
paymentTx.CardHash = verifyResult.CardHash;
paymentTx.RefId = verifyResult.TrackingCode;
}
if (!verifyResult.IsSuccess)
{
_logger.LogWarning(
@@ -101,6 +114,13 @@ public class VerifyDiscountWalletChargeCommandHandler
_context.Transactions.Add(transaction);
await _context.SaveChangesAsync(cancellationToken);
// لینک PaymentTransaction به Transaction داخلی
if (paymentTx != null)
{
paymentTx.TransactionId = transaction.Id;
await _context.SaveChangesAsync(cancellationToken);
}
_logger.LogInformation(
"Discount wallet charged successfully. UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}",
user.Id,
@@ -0,0 +1,73 @@
namespace CMSMicroservice.Domain.Entities.Payment;
/// <summary>
/// جدول ثبت تراکنش‌های درگاه پرداخت آنلاین
/// الگو از PYMS — همه فیلدهای gateway-level اینجا ذخیره می‌شوند
/// جدول Transaction فعلی دست‌نخورده باقی می‌ماند
/// </summary>
public class PaymentTransaction : BaseAuditableEntity
{
// ── اطلاعات درخواست (مرحله Request) ──
/// <summary>نام درگاه (zarinpal, daya, mock, ...)</summary>
public string GatewayProvider { get; set; } = string.Empty;
/// <summary>شناسه مرچنت مورد استفاده</summary>
public string MerchantId { get; set; } = string.Empty;
/// <summary>مبلغ به تومان</summary>
public long Amount { get; set; }
/// <summary>آدرس بازگشت بعد از پرداخت</summary>
public string CallbackUrl { get; set; } = string.Empty;
/// <summary>شرح تراکنش</summary>
public string Description { get; set; } = string.Empty;
/// <summary>شماره موبایل پرداخت‌کننده</summary>
public string? Mobile { get; set; }
/// <summary>شناسه کاربر</summary>
public long? UserId { get; set; }
// ── پاسخ درخواست (بعد از فراخوانی Request API) ──
/// <summary>کد وضعیت درخواست (100=موفق در زرین‌پال)</summary>
public int? RequestStatusCode { get; set; }
/// <summary>پیام درخواست</summary>
public string? RequestStatusMessage { get; set; }
/// <summary>Authority — کلید یکتای تراکنش در درگاه</summary>
public string? Authority { get; set; }
// ── وضعیت پرداخت ──
/// <summary>آیا پرداخت موفق بوده؟</summary>
public bool PaymentStatus { get; set; }
// ── نتیجه تأیید (بعد از Verify API) ──
/// <summary>کد وضعیت verify (100=موفق، 101=قبلاً تأیید شده)</summary>
public int? VerificationStatusCode { get; set; }
/// <summary>پیام verify</summary>
public string? VerificationStatusMessage { get; set; }
/// <summary>هش کارت بانکی</summary>
public string? CardHash { get; set; }
/// <summary>شماره کارت ماسک‌شده (مثلاً 6037-****-****-1234)</summary>
public string? CardPan { get; set; }
/// <summary>شماره مرجع بانکی (RefId عددی زرین‌پال — کد پیگیری)</summary>
public string? RefId { get; set; }
// ── ارتباط با سیستم داخلی ──
/// <summary>شناسه Transaction داخلی (جدول Transactions فعلی)</summary>
public long? TransactionId { get; set; }
/// <summary>شناسه سفارش (اگر مرتبط با سفارش باشد)</summary>
public string? OrderId { get; set; }
}
@@ -94,6 +94,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
// Payment
public DbSet<ManualPayment> ManualPayments => Set<ManualPayment>();
public DbSet<PaymentTransaction> PaymentTransactions => Set<PaymentTransaction>();
// Message
public DbSet<PublicMessage> PublicMessages => Set<PublicMessage>();
@@ -0,0 +1,51 @@
using CMSMicroservice.Domain.Entities.Payment;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace CMSMicroservice.Infrastructure.Persistence.Configurations;
public class PaymentTransactionConfiguration : IEntityTypeConfiguration<PaymentTransaction>
{
public void Configure(EntityTypeBuilder<PaymentTransaction> builder)
{
builder.HasQueryFilter(p => !p.IsDeleted);
builder.Ignore(entity => entity.DomainEvents);
builder.HasKey(entity => entity.Id);
builder.Property(entity => entity.Id).UseIdentityColumn();
// اطلاعات درخواست
builder.Property(e => e.GatewayProvider).IsRequired().HasMaxLength(50);
builder.Property(e => e.MerchantId).IsRequired().HasMaxLength(200);
builder.Property(e => e.Amount).IsRequired();
builder.Property(e => e.CallbackUrl).IsRequired().HasMaxLength(500);
builder.Property(e => e.Description).IsRequired().HasMaxLength(500);
builder.Property(e => e.Mobile).HasMaxLength(20);
builder.Property(e => e.UserId);
// پاسخ درخواست
builder.Property(e => e.RequestStatusCode);
builder.Property(e => e.RequestStatusMessage).HasMaxLength(500);
builder.Property(e => e.Authority).HasMaxLength(200);
// وضعیت پرداخت
builder.Property(e => e.PaymentStatus).IsRequired();
// نتیجه verify
builder.Property(e => e.VerificationStatusCode);
builder.Property(e => e.VerificationStatusMessage).HasMaxLength(500);
builder.Property(e => e.CardHash).HasMaxLength(200);
builder.Property(e => e.CardPan).HasMaxLength(30);
builder.Property(e => e.RefId).HasMaxLength(200);
// ارتباط داخلی
builder.Property(e => e.TransactionId);
builder.Property(e => e.OrderId).HasMaxLength(100);
// ایندکس‌ها
builder.HasIndex(e => e.Authority);
builder.HasIndex(e => e.GatewayProvider);
builder.HasIndex(e => e.UserId);
builder.HasIndex(e => e.TransactionId);
builder.HasIndex(e => e.RefId);
}
}
@@ -0,0 +1,89 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddPaymentTransactionTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PaymentTransactions",
schema: "CMS",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
GatewayProvider = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
MerchantId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Amount = table.Column<long>(type: "bigint", nullable: false),
CallbackUrl = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
Mobile = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
UserId = table.Column<long>(type: "bigint", nullable: true),
RequestStatusCode = table.Column<int>(type: "int", nullable: true),
RequestStatusMessage = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
Authority = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
PaymentStatus = table.Column<bool>(type: "bit", nullable: false),
VerificationStatusCode = table.Column<int>(type: "int", nullable: true),
VerificationStatusMessage = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
CardHash = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
CardPan = table.Column<string>(type: "nvarchar(30)", maxLength: 30, nullable: true),
RefId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
TransactionId = table.Column<long>(type: "bigint", nullable: true),
OrderId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
Created = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastModified = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PaymentTransactions", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_PaymentTransactions_Authority",
schema: "CMS",
table: "PaymentTransactions",
column: "Authority");
migrationBuilder.CreateIndex(
name: "IX_PaymentTransactions_GatewayProvider",
schema: "CMS",
table: "PaymentTransactions",
column: "GatewayProvider");
migrationBuilder.CreateIndex(
name: "IX_PaymentTransactions_RefId",
schema: "CMS",
table: "PaymentTransactions",
column: "RefId");
migrationBuilder.CreateIndex(
name: "IX_PaymentTransactions_TransactionId",
schema: "CMS",
table: "PaymentTransactions",
column: "TransactionId");
migrationBuilder.CreateIndex(
name: "IX_PaymentTransactions_UserId",
schema: "CMS",
table: "PaymentTransactions",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PaymentTransactions",
schema: "CMS");
}
}
}
@@ -2328,6 +2328,114 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.ToTable("ManualPayments", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.PaymentTransaction", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<long>("Amount")
.HasColumnType("bigint");
b.Property<string>("Authority")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("CallbackUrl")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("CardHash")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("CardPan")
.HasMaxLength(30)
.HasColumnType("nvarchar(30)");
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("GatewayProvider")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("MerchantId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("Mobile")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<string>("OrderId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("PaymentStatus")
.HasColumnType("bit");
b.Property<string>("RefId")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<int?>("RequestStatusCode")
.HasColumnType("int");
b.Property<string>("RequestStatusMessage")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<long?>("TransactionId")
.HasColumnType("bigint");
b.Property<long?>("UserId")
.HasColumnType("bigint");
b.Property<int?>("VerificationStatusCode")
.HasColumnType("int");
b.Property<string>("VerificationStatusMessage")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.HasKey("Id");
b.HasIndex("Authority");
b.HasIndex("GatewayProvider");
b.HasIndex("RefId");
b.HasIndex("TransactionId");
b.HasIndex("UserId");
b.ToTable("PaymentTransactions", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b =>
{
b.Property<long>("Id")
@@ -221,6 +221,9 @@ public class ZarinPalPaymentService : IPaymentGatewayService
RefId = refId,
TrackingCode = result.Data.RefId?.ToString(),
Amount = (result.Data.Amount ?? 0) / 10m, // ریال → تومان
CardPan = result.Data.CardPan,
CardHash = result.Data.CardHash,
VerificationCode = result.Data.Code,
Message = result.Data.Code == 101
? "تراکنش قبلاً تأیید شده"
: "تراکنش موفق"
@@ -90,6 +90,20 @@ public class PaymentCallbackController : ControllerBase
paymentSuccess = verifyResult.IsSuccess;
refId = verifyResult.TrackingCode ?? verifyResult.RefId;
// آپدیت PaymentTransaction با نتیجه verify
var paymentTx = await _context.PaymentTransactions
.FirstOrDefaultAsync(pt => pt.Authority == authority, cancellationToken);
if (paymentTx != null)
{
paymentTx.PaymentStatus = verifyResult.IsSuccess;
paymentTx.VerificationStatusCode = verifyResult.VerificationCode;
paymentTx.VerificationStatusMessage = verifyResult.Message;
paymentTx.CardPan = verifyResult.CardPan;
paymentTx.CardHash = verifyResult.CardHash;
paymentTx.RefId = verifyResult.TrackingCode;
await _context.SaveChangesAsync(cancellationToken);
}
_logger.LogInformation(
"Payment verification for Order #{OrderId}: Success={Success}, RefId={RefId}, Message={Message}",
orderId, paymentSuccess, refId, verifyResult.Message);
@@ -14,6 +14,7 @@ using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages;
using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails;
using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities.Payment;
using AppModels = CMSMicroservice.Application.Common.Models;
using Grpc.Core;
using Google.Protobuf.WellKnownTypes;
@@ -23,6 +24,7 @@ using CMSMicroservice.Protobuf.Protos;
using Microsoft.EntityFrameworkCore;
using MediatR;
using Mapster;
using Microsoft.Extensions.Configuration;
namespace CMSMicroservice.WebApi.Services;
public class PackageService : PackageContract.PackageContractBase
@@ -32,19 +34,22 @@ public class PackageService : PackageContract.PackageContractBase
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUserService;
private readonly IPaymentGatewayService _paymentGateway;
private readonly IConfiguration _configuration;
public PackageService(
IDispatchRequestToCQRS dispatchRequestToCQRS,
ISender sender,
IApplicationDbContext context,
ICurrentUserService currentUserService,
IPaymentGatewayService paymentGateway)
IPaymentGatewayService paymentGateway,
IConfiguration configuration)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
_context = context;
_currentUserService = currentUserService;
_paymentGateway = paymentGateway;
_configuration = configuration;
}
public override async Task<CreateNewPackageResponse> CreateNewPackage(CreateNewPackageRequest request, ServerCallContext context)
{
@@ -225,6 +230,25 @@ public class PackageService : PackageContract.PackageContractBase
// Save RefId
transaction.RefId = paymentResult.RefId;
// ثبت PaymentTransaction
var paymentTx = new PaymentTransaction
{
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
Amount = package.Price,
CallbackUrl = request.CallbackUrl,
Description = $"خرید پکیج {package.Title}",
Mobile = user?.Mobile,
UserId = userId,
RequestStatusCode = 100,
RequestStatusMessage = "Success",
Authority = paymentResult.RefId,
PaymentStatus = false,
TransactionId = transaction.Id,
OrderId = purchase.Id.ToString()
};
_context.PaymentTransactions.Add(paymentTx);
await _context.SaveChangesAsync(context.CancellationToken);
return new CustomerPurchasePackageResponse
@@ -275,6 +299,19 @@ public class PackageService : PackageContract.PackageContractBase
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
request.Authority, request.Status, context.CancellationToken);
// آپدیت PaymentTransaction
var paymentTx = await _context.PaymentTransactions
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken);
if (paymentTx != null)
{
paymentTx.PaymentStatus = verifyResult.IsSuccess;
paymentTx.VerificationStatusCode = verifyResult.VerificationCode;
paymentTx.VerificationStatusMessage = verifyResult.Message;
paymentTx.CardPan = verifyResult.CardPan;
paymentTx.CardHash = verifyResult.CardHash;
paymentTx.RefId = verifyResult.TrackingCode;
}
if (verifyResult.IsSuccess && transaction != null)
{
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success;
@@ -10,11 +10,13 @@ using CMSMicroservice.Application.TransactionsCQ.Commands.RefundTransaction;
using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction;
using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities.Payment;
using AppModels = CMSMicroservice.Application.Common.Models;
using Grpc.Core;
using MediatR;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.Linq;
namespace CMSMicroservice.WebApi.Services;
@@ -25,19 +27,22 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUserService;
private readonly IPaymentGatewayService _paymentGateway;
private readonly IConfiguration _configuration;
public TransactionsService(
IDispatchRequestToCQRS dispatchRequestToCQRS,
ISender sender,
IApplicationDbContext context,
ICurrentUserService currentUserService,
IPaymentGatewayService paymentGateway)
IPaymentGatewayService paymentGateway,
IConfiguration configuration)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
_context = context;
_currentUserService = currentUserService;
_paymentGateway = paymentGateway;
_configuration = configuration;
}
public override async Task<CreateNewTransactionsResponse> CreateNewTransactions(CreateNewTransactionsRequest request, ServerCallContext context)
{
@@ -179,6 +184,24 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
// Save RefId from gateway
transaction.RefId = paymentResult.RefId;
// ثبت PaymentTransaction
var paymentTx = new PaymentTransaction
{
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
Amount = request.Amount,
CallbackUrl = request.CallbackUrl,
Description = request.Description ?? "پرداخت آنلاین",
Mobile = request.Mobile ?? user?.Mobile,
UserId = userId,
RequestStatusCode = 100,
RequestStatusMessage = "Success",
Authority = paymentResult.RefId,
PaymentStatus = false,
TransactionId = transaction.Id
};
_context.PaymentTransactions.Add(paymentTx);
await _context.SaveChangesAsync(context.CancellationToken);
return new CustomerPaymentRequestResponse
@@ -216,6 +239,19 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
request.Authority, request.Status, context.CancellationToken);
// آپدیت PaymentTransaction
var paymentTx = await _context.PaymentTransactions
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken);
if (paymentTx != null)
{
paymentTx.PaymentStatus = verifyResult.IsSuccess;
paymentTx.VerificationStatusCode = verifyResult.VerificationCode;
paymentTx.VerificationStatusMessage = verifyResult.Message;
paymentTx.CardPan = verifyResult.CardPan;
paymentTx.CardHash = verifyResult.CardHash;
paymentTx.RefId = verifyResult.TrackingCode;
}
if (verifyResult.IsSuccess)
{
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success;