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
@@ -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")