saving the payment details in database
This commit is contained in:
parent
04f1917332
commit
d29b061799
@ -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)
|
||||
{
|
||||
|
||||
6713
Marco.Pms.DataAccess/Migrations/20251027041510_Added_PaymentDetails_Table.Designer.cs
generated
Normal file
6713
Marco.Pms.DataAccess/Migrations/20251027041510_Added_PaymentDetails_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
|
||||
16
Marco.Pms.Model/PaymentGetway/PaymentDetail.cs
Normal file
16
Marco.Pms.Model/PaymentGetway/PaymentDetail.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
36
Marco.Pms.Services/Service/AesEncryption.cs
Normal file
36
Marco.Pms.Services/Service/AesEncryption.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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="
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user