From 04f191733253dd8c6468d7cc9387834afff9df23 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Sat, 25 Oct 2025 15:57:16 +0530 Subject: [PATCH] Added the payment details in verify payment API --- .../PaymentGetway/RazorpayPaymentDetails.cs | 77 ++++ .../Controllers/PaymentController.cs | 68 +++- Marco.Pms.Services/Helpers/PaymentHelper.cs | 57 --- Marco.Pms.Services/Program.cs | 2 +- Marco.Pms.Services/Service/RazorpayService.cs | 336 ++++++++++++++++++ .../ServiceInterfaces/IRazorpayService.cs | 13 + .../appsettings.Development.json | 4 + 7 files changed, 485 insertions(+), 72 deletions(-) create mode 100644 Marco.Pms.Model/ViewModels/PaymentGetway/RazorpayPaymentDetails.cs delete mode 100644 Marco.Pms.Services/Helpers/PaymentHelper.cs create mode 100644 Marco.Pms.Services/Service/RazorpayService.cs create mode 100644 Marco.Pms.Services/Service/ServiceInterfaces/IRazorpayService.cs diff --git a/Marco.Pms.Model/ViewModels/PaymentGetway/RazorpayPaymentDetails.cs b/Marco.Pms.Model/ViewModels/PaymentGetway/RazorpayPaymentDetails.cs new file mode 100644 index 0000000..8e7e76d --- /dev/null +++ b/Marco.Pms.Model/ViewModels/PaymentGetway/RazorpayPaymentDetails.cs @@ -0,0 +1,77 @@ +namespace Marco.Pms.Model.ViewModels.PaymentGetway +{ + public class RazorpayPaymentDetails + { + public string? PaymentId { get; set; } + public string? OrderId { get; set; } + public decimal Amount { get; set; } + public string? Currency { get; set; } + public string? Status { get; set; } // created, authorized, captured, refunded, failed + public string? Method { get; set; } // card, netbanking, wallet, upi + public string? Email { get; set; } + public string? Contact { get; set; } + public string? Description { get; set; } + public string? CustomerName { get; set; } + public DateTime CreatedAt { get; set; } + + // Payment method specific details + public CardDetails? CardDetails { get; set; } + public BankDetails? BankDetails { get; set; } + public UpiDetails? UpiDetails { get; set; } + public WalletDetails? WalletDetails { get; set; } + + // Fee and tax + public decimal Fee { get; set; } + public decimal Tax { get; set; } + + // Error details (if payment failed) + public string? ErrorCode { get; set; } + public string? ErrorDescription { get; set; } + + // Additional flags + public bool InternationalPayment { get; set; } + public bool Captured { get; set; } + } + + public class CardDetails + { + public string? CardId { get; set; } + public string? Last4Digits { get; set; } + public string? Network { get; set; } // Visa, MasterCard, Amex, RuPay + public string? CardType { get; set; } // credit, debit, prepaid + public string? Issuer { get; set; } // Bank name + public bool International { get; set; } + public bool Emi { get; set; } + public string? SubType { get; set; } // consumer, business + } + + public class BankDetails + { + public string? Bank { get; set; } + public string? BankCode { get; set; } + } + + public class UpiDetails + { + public string? Vpa { get; set; } // UPI ID + public string? Provider { get; set; } + } + + public class WalletDetails + { + public string? WalletName { get; set; } // paytm, phonepe, amazonpay, freecharge, jiomoney, olamoney + } + + public class RazorpayOrderDetails + { + public string? OrderId { get; set; } + public decimal Amount { get; set; } + public string? Currency { get; set; } + public string? Status { get; set; } + public string? Receipt { get; set; } + public DateTime CreatedAt { get; set; } + public decimal AmountPaid { get; set; } + public decimal AmountDue { get; set; } + public int Attempts { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/PaymentController.cs b/Marco.Pms.Services/Controllers/PaymentController.cs index 5418380..4b91706 100644 --- a/Marco.Pms.Services/Controllers/PaymentController.cs +++ b/Marco.Pms.Services/Controllers/PaymentController.cs @@ -1,7 +1,8 @@ using Marco.Pms.Model.Dtos.PaymentGetway; using Marco.Pms.Model.Utilities; -using Marco.Pms.Services.Helpers; +using Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Helpers; +using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Mvc; namespace Marco.Pms.Services.Controllers @@ -11,13 +12,15 @@ namespace Marco.Pms.Services.Controllers public class PaymentController : ControllerBase { private readonly UserHelper _userHelper; - private readonly PaymentHelper _paymentHelper; + private readonly ILoggingService _logger; + private readonly IRazorpayService _razorpayService; private readonly Guid tenantId; private readonly Guid organizaionId; - public PaymentController(UserHelper userHelper, PaymentHelper paymentHelper) + public PaymentController(UserHelper userHelper, ILoggingService logger, IRazorpayService razorpayService) { _userHelper = userHelper; - _paymentHelper = paymentHelper; + _logger = logger; + _razorpayService = razorpayService; tenantId = userHelper.GetTenantId(); organizaionId = userHelper.GetCurrentOrganizationId(); } @@ -28,7 +31,7 @@ namespace Marco.Pms.Services.Controllers var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); try { - var response = _paymentHelper.CreateOrder(model.Amount, loggedInEmployee, tenantId); + var response = _razorpayService.CreateOrder(model.Amount, loggedInEmployee, tenantId); return Ok(ApiResponse.SuccessResponse(response, "Payment created successfully", 200)); } catch (Exception ex) @@ -51,20 +54,57 @@ namespace Marco.Pms.Services.Controllers [HttpPost("verify-payment")] public IActionResult VerifyPayment([FromBody] PaymentVerificationRequest request) { - string payload = request.OrderId + "|" + request.PaymentId; - string actualSignature = request.Signature ?? ""; - string expectedSignature = _paymentHelper.GetExpectedSignature(payload); + try + { + _logger.LogInfo("Payment verification started for OrderId: {OrderId}, PaymentId: {PaymentId}", + request.OrderId ?? "", request.PaymentId ?? ""); - if (actualSignature == expectedSignature) - { - // Payment is verified, process accordingly e.g. update tenant payment details - return Ok(new { status = "success" }); + // Validate request + if (string.IsNullOrEmpty(request.OrderId) || string.IsNullOrEmpty(request.PaymentId) || string.IsNullOrEmpty(request.Signature)) + { + _logger.LogWarning("Payment verification failed - Missing required parameters"); + return BadRequest(ApiResponse.ErrorResponse("Missing required parameters", 400)); + } + + // Verify signature + string payload = request.OrderId + "|" + request.PaymentId; + string actualSignature = request.Signature; + string expectedSignature = _razorpayService.GetExpectedSignature(payload); + + if (actualSignature == expectedSignature) + { + _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); + + _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.SuccessResponse(responseData, "Payment verified successfully", 200)); + } + else + { + _logger.LogWarning("Payment signature verification failed for OrderId: {OrderId}", request.OrderId); + return BadRequest(ApiResponse.ErrorResponse("Invalid signature - Payment verification failed", 400)); + } } - else + catch (Exception ex) { - return BadRequest(new { status = "failure", message = "Invalid signature" }); + _logger.LogError(ex, "Error during payment verification for OrderId: {OrderId}", request.OrderId ?? ""); + return StatusCode(500, ApiResponse.ErrorResponse("An error occurred during payment verification", 500)); } } + } } diff --git a/Marco.Pms.Services/Helpers/PaymentHelper.cs b/Marco.Pms.Services/Helpers/PaymentHelper.cs deleted file mode 100644 index fce3f8f..0000000 --- a/Marco.Pms.Services/Helpers/PaymentHelper.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Marco.Pms.Model.Employees; -using Marco.Pms.Model.ViewModels.PaymentGetway; -using Razorpay.Api; -using System.Security.Cryptography; -using System.Text; - -namespace Marco.Pms.Services.Helpers -{ - public class PaymentHelper - { - private readonly IConfiguration _configuration; - private readonly string key = "YOUR_RAZORPAY_KEY"; - private readonly string secret = "YOUR_RAZORPAY_SECRET"; - public PaymentHelper(IConfiguration configuration) - { - _configuration = configuration; - key = configuration["Razorpay:Key"] ?? ""; - secret = configuration["Razorpay:Secret"] ?? ""; - } - - public CreateOrderVM CreateOrder(double amount, Employee loggedInEmployee, Guid tenantId) - { - RazorpayClient client = new RazorpayClient(key, secret); - - var receipt = $"rec_{Guid.NewGuid()}"; - var length = receipt.Length; - - Dictionary options = new Dictionary - { - { "amount", amount * 100 }, // amount in paise - { "currency", "INR" }, - { "receipt", receipt}, - { "payment_capture", 1 } - }; - - Order order = client.Order.Create(options); - var response = new CreateOrderVM - { - OrderId = order["id"], - Key = key - }; - return response; - } - - public string GetExpectedSignature(string payload) - { - string expectedSignature; - using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret))) - { - var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)); - expectedSignature = Convert.ToHexString(hash).ToLower(); - } - - return expectedSignature; - } - } -} diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index de0921e..86a3e89 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -183,6 +183,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); #endregion #region Helpers @@ -191,7 +192,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Marco.Pms.Services/Service/RazorpayService.cs b/Marco.Pms.Services/Service/RazorpayService.cs new file mode 100644 index 0000000..ce7b88a --- /dev/null +++ b/Marco.Pms.Services/Service/RazorpayService.cs @@ -0,0 +1,336 @@ +using Marco.Pms.Model.Employees; +using Marco.Pms.Model.ViewModels.PaymentGetway; +using Marco.Pms.Services.Service.ServiceInterfaces; +using MarcoBMS.Services.Service; +using Newtonsoft.Json; +using Razorpay.Api; +using System.Security.Cryptography; +using System.Text; + +namespace Marco.Pms.Services.Service +{ + public class RazorpayService : IRazorpayService + { + private readonly RazorpayClient _razorpayClient; + private readonly ILoggingService _logger; + private readonly IConfiguration _configuration; + private readonly string key; + private readonly string secret; + public RazorpayService(IConfiguration configuration, ILoggingService logger) + { + _logger = logger; + _configuration = configuration; + key = configuration["Razorpay:Key"] ?? ""; + secret = configuration["Razorpay:Secret"] ?? ""; + _razorpayClient = new RazorpayClient(key, secret); + } + + public CreateOrderVM CreateOrder(double amount, Employee loggedInEmployee, Guid tenantId) + { + RazorpayClient client = new RazorpayClient(key, secret); + + var receipt = $"rec_{Guid.NewGuid()}"; + var length = receipt.Length; + + Dictionary options = new Dictionary + { + { "amount", amount * 100 }, // amount in paise + { "currency", "INR" }, + { "receipt", receipt}, + { "payment_capture", 1 } + }; + + Order order = client.Order.Create(options); + var response = new CreateOrderVM + { + OrderId = order["id"], + Key = key + }; + return response; + } + + public string GetExpectedSignature(string payload) + { + string expectedSignature; + using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret))) + { + var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)); + expectedSignature = Convert.ToHexString(hash).ToLower(); + } + + return expectedSignature; + } + + /// + /// Fetch complete payment details from Razorpay including card details + /// + /// Razorpay Payment ID + /// Complete payment details with card information + public RazorpayPaymentDetails? GetPaymentDetailsAsync(string paymentId) + { + try + { + _logger.LogInfo("Fetching payment details from Razorpay for PaymentId: {PaymentId}", paymentId); + + // Fetch payment details from Razorpay + Payment payment = _razorpayClient.Payment.Fetch(paymentId); + + // Extract customer name from notes or fetch from customer API + string customerName = ExtractCustomerName(payment); + + // Map to custom model with all details + var paymentDetails = new RazorpayPaymentDetails + { + PaymentId = payment.Attributes["id"]?.ToString(), + OrderId = payment.Attributes["order_id"]?.ToString(), + Amount = Convert.ToDecimal(payment.Attributes["amount"]) / 100, // Convert from paise to rupees + Currency = payment.Attributes["currency"]?.ToString(), + Status = payment.Attributes["status"]?.ToString(), + Method = payment.Attributes["method"]?.ToString(), + Email = payment.Attributes["email"]?.ToString(), + Contact = payment.Attributes["contact"]?.ToString(), + Description = payment.Attributes["description"]?.ToString(), + CustomerName = customerName, + CreatedAt = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(payment.Attributes["created_at"])).DateTime, + + // Card details (if payment method is card) + CardDetails = payment.Attributes["method"]?.ToString() == "card" + ? ExtractCardDetails(payment) + : null, + + // Bank details (if payment method is netbanking) + BankDetails = payment.Attributes["method"]?.ToString() == "netbanking" + ? ExtractBankDetails(payment) + : null, + + // UPI details (if payment method is upi) + UpiDetails = payment.Attributes["method"]?.ToString() == "upi" + ? ExtractUpiDetails(payment) + : null, + + // Wallet details (if payment method is wallet) + WalletDetails = payment.Attributes["method"]?.ToString() == "wallet" + ? ExtractWalletDetails(payment) + : null, + + // Additional details + Fee = payment.Attributes["fee"] != null + ? Convert.ToDecimal(payment.Attributes["fee"]) / 100 + : 0, + Tax = payment.Attributes["tax"] != null + ? Convert.ToDecimal(payment.Attributes["tax"]) / 100 + : 0, + ErrorCode = payment.Attributes["error_code"]?.ToString(), + ErrorDescription = payment.Attributes["error_description"]?.ToString(), + InternationalPayment = Convert.ToBoolean(payment.Attributes["international"] ?? false), + Captured = Convert.ToBoolean(payment.Attributes["captured"] ?? false) + }; + + _logger.LogInfo("Payment details fetched successfully from Razorpay for PaymentId: {PaymentId}", paymentId); + return paymentDetails; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching payment details from Razorpay for PaymentId: {PaymentId}", paymentId); + return null; + } + } + + /// + /// Extract card details from payment + /// + private CardDetails? ExtractCardDetails(Payment payment) + { + try + { + var cardObj = payment.Attributes["card"]; + if (cardObj == null) return null; + + string json = JsonConvert.SerializeObject(cardObj); + Dictionary? cardDict = JsonConvert.DeserializeObject>(json); + + return new CardDetails + { + CardId = cardDict?["id"]?.ToString(), + Last4Digits = cardDict?["last4"]?.ToString(), + Network = cardDict?["network"]?.ToString(), // Visa, MasterCard, Amex, etc. + CardType = cardDict?["type"]?.ToString(), // credit, debit, prepaid + Issuer = cardDict?["issuer"]?.ToString(), // Bank name + International = Convert.ToBoolean(cardDict?["international"] ?? false), + Emi = Convert.ToBoolean(cardDict?["emi"] ?? false), + SubType = cardDict?["sub_type"]?.ToString() // consumer, business + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error extracting card details"); + return null; + } + } + + /// + /// Extract bank details from payment + /// + private BankDetails? ExtractBankDetails(Payment payment) + { + try + { + return new BankDetails + { + Bank = payment.Attributes["bank"]?.ToString(), + BankCode = payment.Attributes["bank_code"]?.ToString() + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error extracting bank details"); + return null; + } + } + + /// + /// Extract UPI details from payment + /// + private UpiDetails? ExtractUpiDetails(Payment payment) + { + try + { + var vpaObj = payment.Attributes["vpa"]; + + return new UpiDetails + { + Vpa = vpaObj?.ToString(), // UPI ID + Provider = payment.Attributes["provider"]?.ToString() + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error extracting UPI details"); + return null; + } + } + + /// + /// Extract wallet details from payment + /// + private WalletDetails? ExtractWalletDetails(Payment payment) + { + try + { + return new WalletDetails + { + WalletName = payment.Attributes["wallet"]?.ToString() // paytm, phonepe, amazonpay, etc. + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error extracting wallet details"); + return null; + } + } + + /// + /// Extract customer name from payment object + /// + private string ExtractCustomerName(Payment payment) + { + try + { + // Option 1: Get from notes (if you passed customer name during order creation) + var notesObj = payment.Attributes["notes"]; + if (notesObj != null) + { + var notesDict = notesObj as Dictionary; + if (notesDict != null && notesDict.ContainsKey("customer_name")) + { + return notesDict["customer_name"]?.ToString() ?? "CustomerName"; + } + if (notesDict != null && notesDict.ContainsKey("name")) + { + return notesDict["name"]?.ToString() ?? "CustomerName"; + } + } + + // Option 2: Get from customer ID and fetch customer details + var customerId = payment.Attributes["customer_id"]?.ToString(); + if (!string.IsNullOrEmpty(customerId)) + { + try + { + Customer customer = _razorpayClient.Customer.Fetch(customerId); + return customer.Attributes["name"]?.ToString() ?? "CustomerName"; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching customer details for CustomerId: {CustomerId}", customerId); + } + } + + // Option 3: Extract from card holder name (if available) + if (payment.Attributes["method"]?.ToString() == "card") + { + var cardObj = payment.Attributes["card"]; + if (cardObj != null) + { + string json = JsonConvert.SerializeObject(cardObj); + Dictionary? dictionary = JsonConvert.DeserializeObject>(json); + + var cardName = dictionary?["name"]?.ToString(); + if (!string.IsNullOrEmpty(cardName)) + { + return cardName; + } + } + } + + // Option 4: Use email as fallback + return payment.Attributes["email"]?.ToString() ?? "Customer"; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error extracting customer name from payment"); + return "Customer"; + } + } + + /// + /// Fetch order details from Razorpay + /// + /// Razorpay Order ID + /// Order details + 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; + } + } + } +} diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IRazorpayService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IRazorpayService.cs new file mode 100644 index 0000000..6d82c19 --- /dev/null +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IRazorpayService.cs @@ -0,0 +1,13 @@ +using Marco.Pms.Model.Employees; +using Marco.Pms.Model.ViewModels.PaymentGetway; + +namespace Marco.Pms.Services.Service.ServiceInterfaces +{ + public interface IRazorpayService + { + CreateOrderVM CreateOrder(double amount, Employee loggedInEmployee, Guid tenantId); + string GetExpectedSignature(string payload); + RazorpayPaymentDetails? GetPaymentDetailsAsync(string paymentId); + RazorpayOrderDetails? GetOrderDetailsAsync(string orderId); + } +} diff --git a/Marco.Pms.Services/appsettings.Development.json b/Marco.Pms.Services/appsettings.Development.json index 541cc61..4c29450 100644 --- a/Marco.Pms.Services/appsettings.Development.json +++ b/Marco.Pms.Services/appsettings.Development.json @@ -54,5 +54,9 @@ "Razorpay": { "Key": "rzp_test_RXCzgEcXucbuAi", "Secret": "YNAVBXxRsDg8Oat4M1C3m09W" + }, + "Encryption": { + "PaymentKey": "4oZV1Za+zqD0CgTvRrj5zUVQeMxok5MSTz6c3wsECdM=", + "CollectionKey": "9bVvYrbL1uB+v6TjWRsJ8N8VFI8rE7e7hVhVSKg3JZU=" } }