feat: add PaymentTransaction table for gateway-level tracking
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 9m6s
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:
@@ -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>
|
||||
|
||||
+19
@@ -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;
|
||||
|
||||
+30
-3
@@ -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;
|
||||
|
||||
+20
@@ -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>();
|
||||
|
||||
+51
@@ -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);
|
||||
}
|
||||
}
|
||||
+4518
File diff suppressed because it is too large
Load Diff
+89
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user