saving the payment details in database

This commit is contained in:
ashutosh.nehete 2025-10-27 09:49:23 +05:30
parent 04f1917332
commit d29b061799
13 changed files with 6987 additions and 61 deletions

View File

@ -11,6 +11,7 @@ using Marco.Pms.Model.Forum;
using Marco.Pms.Model.Mail;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.OrganizationModel;
using Marco.Pms.Model.PaymentGetway;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Roles;
using Marco.Pms.Model.TenantModels;
@ -141,6 +142,8 @@ namespace Marco.Pms.DataAccess.Data
public DbSet<ReceivedInvoicePayment> ReceivedInvoicePayments { get; set; }
public DbSet<PaymentAdjustmentHead> PaymentAdjustmentHeads { get; set; }
public DbSet<PaymentDetail> PaymentDetails { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,46 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_PaymentDetails_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PaymentDetails",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
PaymentId = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
OrderId = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Method = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
EncryptedDetails = table.Column<byte[]>(type: "longblob", nullable: true),
Nonce = table.Column<byte[]>(type: "longblob", nullable: true),
Tag = table.Column<byte[]>(type: "longblob", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PaymentDetails", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PaymentDetails");
}
}
}

View File

@ -4124,6 +4124,45 @@ namespace Marco.Pms.DataAccess.Migrations
b.ToTable("TenantOrgMappings");
});
modelBuilder.Entity("Marco.Pms.Model.PaymentGetway.PaymentDetail", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<byte[]>("EncryptedDetails")
.HasColumnType("longblob");
b.Property<string>("Method")
.IsRequired()
.HasColumnType("longtext");
b.Property<byte[]>("Nonce")
.HasColumnType("longblob");
b.Property<string>("OrderId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PaymentId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.Property<byte[]>("Tag")
.HasColumnType("longblob");
b.HasKey("Id");
b.ToTable("PaymentDetails");
});
modelBuilder.Entity("Marco.Pms.Model.Projects.Building", b =>
{
b.Property<Guid>("Id")

View File

@ -0,0 +1,16 @@
namespace Marco.Pms.Model.PaymentGetway
{
public class PaymentDetail
{
public Guid Id { get; set; }
public string PaymentId { get; set; } = string.Empty;
public string OrderId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty; // created, authorized, captured, refunded, failed
public string Method { get; set; } = string.Empty;
public byte[]? EncryptedDetails { get; set; }
public byte[]? Nonce { get; set; }
public byte[]? Tag { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}

View File

@ -1,5 +1,10 @@
namespace Marco.Pms.Model.ViewModels.PaymentGetway
{
public class PaymentDetailsVM
{
public RazorpayPaymentDetails? RazorpayPaymentDetails { get; set; }
public RazorpayOrderDetails? RazorpayOrderDetails { get; set; }
}
public class RazorpayPaymentDetails
{
public string? PaymentId { get; set; }

View File

@ -52,7 +52,7 @@ namespace Marco.Pms.Services.Controllers
}
[HttpPost("verify-payment")]
public IActionResult VerifyPayment([FromBody] PaymentVerificationRequest request)
public async Task<IActionResult> VerifyPayment([FromBody] PaymentVerificationRequest request)
{
try
{
@ -76,21 +76,11 @@ namespace Marco.Pms.Services.Controllers
_logger.LogInfo("Payment signature verified successfully for OrderId: {OrderId}", request.OrderId);
// Fetch complete payment details from Razorpay including card details
var razorpayPaymentDetails = _razorpayService.GetPaymentDetailsAsync(request.PaymentId);
// Fetch order details from Razorpay
var razorpayOrderDetails = _razorpayService.GetOrderDetailsAsync(request.OrderId);
var response = await _razorpayService.GetPaymentDetails(request.PaymentId);
_logger.LogInfo("Invoice generated and saved for OrderId: {OrderId}", request.OrderId);
// Prepare response with all details
var responseData = new
{
RazorpayPaymentDetails = razorpayPaymentDetails,
RazorpayOrderDetails = razorpayOrderDetails
};
return Ok(ApiResponse<object>.SuccessResponse(responseData, "Payment verified successfully", 200));
return Ok(ApiResponse<object>.SuccessResponse(response, "Payment verified successfully", 200));
}
else
{
@ -105,6 +95,13 @@ namespace Marco.Pms.Services.Controllers
}
}
[HttpGet("get/payment-details/{id}")]
public async Task<IActionResult> GetPaymentDetails(Guid id)
{
var paymentsDetails = await _razorpayService.GetPaymentDetailsFromDataBase(id);
return Ok(ApiResponse<object>.SuccessResponse(paymentsDetails, "Payment fetched Successfully", 200));
}
}
}

View File

@ -184,6 +184,7 @@ builder.Services.AddScoped<IMasterService, MasterService>();
builder.Services.AddScoped<IDirectoryService, DirectoryService>();
builder.Services.AddScoped<IFirebaseService, FirebaseService>();
builder.Services.AddScoped<IRazorpayService, RazorpayService>();
builder.Services.AddScoped<IAesEncryption, AesEncryption>();
#endregion
#region Helpers

View File

@ -0,0 +1,36 @@
using Marco.Pms.Services.Service.ServiceInterfaces;
using System.Security.Cryptography;
using System.Text;
namespace Marco.Pms.Services.Service
{
public class AesEncryption : IAesEncryption
{
public (byte[] ciphertext, byte[] nonce, byte[] tag) Encrypt(string plaintext, byte[] key)
{
byte[] autoKey = new byte[32]; // 32 bytes = 256 bits
RandomNumberGenerator.Fill(autoKey);
var stringKey = Convert.ToBase64String(autoKey);
byte[] nonce = RandomNumberGenerator.GetBytes(12);
byte[] plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
byte[] ciphertext = new byte[plaintextBytes.Length];
byte[] tag = new byte[16];
using var aes = new AesGcm(key, 16);
aes.Encrypt(nonce, plaintextBytes, ciphertext, tag);
return (ciphertext, nonce, tag);
}
public string Decrypt(byte[] ciphertext, byte[] nonce, byte[] tag, byte[] key)
{
byte[] plaintext = new byte[ciphertext.Length];
using var aes = new AesGcm(key, 16);
aes.Decrypt(nonce, ciphertext, tag, plaintext);
return Encoding.UTF8.GetString(plaintext);
}
}
}

View File

@ -1,7 +1,10 @@
using Marco.Pms.Model.Employees;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.PaymentGetway;
using Marco.Pms.Model.ViewModels.PaymentGetway;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Service;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Razorpay.Api;
using System.Security.Cryptography;
@ -11,18 +14,28 @@ namespace Marco.Pms.Services.Service
{
public class RazorpayService : IRazorpayService
{
private readonly ApplicationDbContext _context;
private readonly RazorpayClient _razorpayClient;
private readonly ILoggingService _logger;
private readonly IConfiguration _configuration;
private readonly IAesEncryption _aesEncryption;
private readonly string key;
private readonly string secret;
public RazorpayService(IConfiguration configuration, ILoggingService logger)
private readonly byte[] encryptionKey;
public RazorpayService(ApplicationDbContext context, IConfiguration configuration, ILoggingService logger, IAesEncryption aesEncryption)
{
_context = context;
_logger = logger;
_configuration = configuration;
_aesEncryption = aesEncryption;
key = configuration["Razorpay:Key"] ?? "";
secret = configuration["Razorpay:Secret"] ?? "";
_razorpayClient = new RazorpayClient(key, secret);
string stringKey = configuration["Encryption:PaymentKey"] ?? "";
encryptionKey = Convert.FromBase64String(stringKey);
}
public CreateOrderVM CreateOrder(double amount, Employee loggedInEmployee, Guid tenantId)
@ -66,7 +79,7 @@ namespace Marco.Pms.Services.Service
/// </summary>
/// <param name="paymentId">Razorpay Payment ID</param>
/// <returns>Complete payment details with card information</returns>
public RazorpayPaymentDetails? GetPaymentDetailsAsync(string paymentId)
public async Task<PaymentDetailsVM> GetPaymentDetails(string paymentId)
{
try
{
@ -126,16 +139,104 @@ namespace Marco.Pms.Services.Service
Captured = Convert.ToBoolean(payment.Attributes["captured"] ?? false)
};
var razorpayOrderDetails = GetOrderDetails(paymentDetails.OrderId ?? "");
var response = new PaymentDetailsVM
{
RazorpayPaymentDetails = paymentDetails,
RazorpayOrderDetails = razorpayOrderDetails
};
string jsonString = JsonConvert.SerializeObject(response);
var data = _aesEncryption.Encrypt(jsonString, encryptionKey);
var paymentDetail = new PaymentDetail
{
Id = Guid.NewGuid(),
PaymentId = paymentDetails.PaymentId ?? "",
OrderId = paymentDetails.OrderId ?? "",
Status = paymentDetails.Status ?? "",
Method = paymentDetails.Method ?? "",
CreatedAt = DateTime.UtcNow,
EncryptedDetails = data.ciphertext,
Nonce = data.nonce,
Tag = data.tag
};
_context.PaymentDetails.Add(paymentDetail);
await _context.SaveChangesAsync();
_logger.LogInfo("Payment details fetched successfully from Razorpay for PaymentId: {PaymentId}", paymentId);
return paymentDetails;
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching payment details from Razorpay for PaymentId: {PaymentId}", paymentId);
return new PaymentDetailsVM();
}
}
/// <summary>
/// Fetch order details from Razorpay
/// </summary>
/// <param name="orderId">Razorpay Order ID</param>
/// <returns>Order details</returns>
public RazorpayOrderDetails? GetOrderDetails(string orderId)
{
try
{
_logger.LogInfo("Fetching order details from Razorpay for OrderId: {OrderId}", orderId);
Order order = _razorpayClient.Order.Fetch(orderId);
var orderDetails = new RazorpayOrderDetails
{
OrderId = order.Attributes["id"]?.ToString(),
Amount = Convert.ToDecimal(order.Attributes["amount"]) / 100,
Currency = order.Attributes["currency"]?.ToString(),
Status = order.Attributes["status"]?.ToString(),
Receipt = order.Attributes["receipt"]?.ToString(),
CreatedAt = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(order.Attributes["created_at"])).DateTime,
AmountPaid = order.Attributes["amount_paid"] != null
? Convert.ToDecimal(order.Attributes["amount_paid"]) / 100
: 0,
AmountDue = order.Attributes["amount_due"] != null
? Convert.ToDecimal(order.Attributes["amount_due"]) / 100
: 0,
Attempts = Convert.ToInt32(order.Attributes["attempts"] ?? 0)
};
_logger.LogInfo("Order details fetched successfully from Razorpay for OrderId: {OrderId}", orderId);
return orderDetails;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching order details from Razorpay for OrderId: {OrderId}", orderId);
return null;
}
}
public async Task<PaymentDetailsVM> GetPaymentDetailsFromDataBase(Guid id)
{
var projectDetails = await _context.PaymentDetails.FirstOrDefaultAsync(pd => pd.Id == id);
if (projectDetails == null)
{
return new PaymentDetailsVM();
}
string decrypedData = _aesEncryption.Decrypt(projectDetails.EncryptedDetails ?? new byte[32], projectDetails.Nonce ?? new byte[12], projectDetails.Tag ?? new byte[16], encryptionKey);
// Deserialize JSON string to a Department object
PaymentDetailsVM? vm = JsonConvert.DeserializeObject<PaymentDetailsVM>(decrypedData);
if (vm == null)
{
return new PaymentDetailsVM();
}
else
{
return vm;
}
}
/// <summary>
/// Extract card details from payment
/// </summary>
@ -292,45 +393,5 @@ namespace Marco.Pms.Services.Service
return "Customer";
}
}
/// <summary>
/// Fetch order details from Razorpay
/// </summary>
/// <param name="orderId">Razorpay Order ID</param>
/// <returns>Order details</returns>
public RazorpayOrderDetails? GetOrderDetailsAsync(string orderId)
{
try
{
_logger.LogInfo("Fetching order details from Razorpay for OrderId: {OrderId}", orderId);
Order order = _razorpayClient.Order.Fetch(orderId);
var orderDetails = new RazorpayOrderDetails
{
OrderId = order.Attributes["id"]?.ToString(),
Amount = Convert.ToDecimal(order.Attributes["amount"]) / 100,
Currency = order.Attributes["currency"]?.ToString(),
Status = order.Attributes["status"]?.ToString(),
Receipt = order.Attributes["receipt"]?.ToString(),
CreatedAt = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(order.Attributes["created_at"])).DateTime,
AmountPaid = order.Attributes["amount_paid"] != null
? Convert.ToDecimal(order.Attributes["amount_paid"]) / 100
: 0,
AmountDue = order.Attributes["amount_due"] != null
? Convert.ToDecimal(order.Attributes["amount_due"]) / 100
: 0,
Attempts = Convert.ToInt32(order.Attributes["attempts"] ?? 0)
};
_logger.LogInfo("Order details fetched successfully from Razorpay for OrderId: {OrderId}", orderId);
return orderDetails;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching order details from Razorpay for OrderId: {OrderId}", orderId);
return null;
}
}
}
}

View File

@ -0,0 +1,8 @@
namespace Marco.Pms.Services.Service.ServiceInterfaces
{
public interface IAesEncryption
{
(byte[] ciphertext, byte[] nonce, byte[] tag) Encrypt(string plaintext, byte[] key);
string Decrypt(byte[] ciphertext, byte[] nonce, byte[] tag, byte[] key);
}
}

View File

@ -7,7 +7,8 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
{
CreateOrderVM CreateOrder(double amount, Employee loggedInEmployee, Guid tenantId);
string GetExpectedSignature(string payload);
RazorpayPaymentDetails? GetPaymentDetailsAsync(string paymentId);
RazorpayOrderDetails? GetOrderDetailsAsync(string orderId);
Task<PaymentDetailsVM> GetPaymentDetails(string paymentId);
Task<PaymentDetailsVM> GetPaymentDetailsFromDataBase(Guid id);
RazorpayOrderDetails? GetOrderDetails(string orderId);
}
}

View File

@ -9,7 +9,7 @@
"Title": "Dev"
},
"ConnectionStrings": {
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSStage"
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1"
},
"SmtpSettings": {
"SmtpServer": "smtp.gmail.com",
@ -56,7 +56,7 @@
"Secret": "YNAVBXxRsDg8Oat4M1C3m09W"
},
"Encryption": {
"PaymentKey": "4oZV1Za+zqD0CgTvRrj5zUVQeMxok5MSTz6c3wsECdM=",
"PaymentKey": "+V47lEWiolUZOUZcHq/3M6SEd3kPraGJpnJ+K5ni0Oo=",
"CollectionKey": "9bVvYrbL1uB+v6TjWRsJ8N8VFI8rE7e7hVhVSKg3JZU="
}
}