Added the payment details in verify payment API

This commit is contained in:
ashutosh.nehete 2025-10-25 15:57:16 +05:30
parent 1d54af7c00
commit 04f1917332
7 changed files with 485 additions and 72 deletions

View File

@ -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; }
}
}

View File

@ -1,7 +1,8 @@
using Marco.Pms.Model.Dtos.PaymentGetway; using Marco.Pms.Model.Dtos.PaymentGetway;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Marco.Pms.Services.Controllers namespace Marco.Pms.Services.Controllers
@ -11,13 +12,15 @@ namespace Marco.Pms.Services.Controllers
public class PaymentController : ControllerBase public class PaymentController : ControllerBase
{ {
private readonly UserHelper _userHelper; private readonly UserHelper _userHelper;
private readonly PaymentHelper _paymentHelper; private readonly ILoggingService _logger;
private readonly IRazorpayService _razorpayService;
private readonly Guid tenantId; private readonly Guid tenantId;
private readonly Guid organizaionId; private readonly Guid organizaionId;
public PaymentController(UserHelper userHelper, PaymentHelper paymentHelper) public PaymentController(UserHelper userHelper, ILoggingService logger, IRazorpayService razorpayService)
{ {
_userHelper = userHelper; _userHelper = userHelper;
_paymentHelper = paymentHelper; _logger = logger;
_razorpayService = razorpayService;
tenantId = userHelper.GetTenantId(); tenantId = userHelper.GetTenantId();
organizaionId = userHelper.GetCurrentOrganizationId(); organizaionId = userHelper.GetCurrentOrganizationId();
} }
@ -28,7 +31,7 @@ namespace Marco.Pms.Services.Controllers
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
try try
{ {
var response = _paymentHelper.CreateOrder(model.Amount, loggedInEmployee, tenantId); var response = _razorpayService.CreateOrder(model.Amount, loggedInEmployee, tenantId);
return Ok(ApiResponse<object>.SuccessResponse(response, "Payment created successfully", 200)); return Ok(ApiResponse<object>.SuccessResponse(response, "Payment created successfully", 200));
} }
catch (Exception ex) catch (Exception ex)
@ -51,20 +54,57 @@ namespace Marco.Pms.Services.Controllers
[HttpPost("verify-payment")] [HttpPost("verify-payment")]
public IActionResult VerifyPayment([FromBody] PaymentVerificationRequest request) public IActionResult VerifyPayment([FromBody] PaymentVerificationRequest request)
{ {
try
{
_logger.LogInfo("Payment verification started for OrderId: {OrderId}, PaymentId: {PaymentId}",
request.OrderId ?? "", request.PaymentId ?? "");
// 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<object>.ErrorResponse("Missing required parameters", 400));
}
// Verify signature
string payload = request.OrderId + "|" + request.PaymentId; string payload = request.OrderId + "|" + request.PaymentId;
string actualSignature = request.Signature ?? ""; string actualSignature = request.Signature;
string expectedSignature = _paymentHelper.GetExpectedSignature(payload); string expectedSignature = _razorpayService.GetExpectedSignature(payload);
if (actualSignature == expectedSignature) if (actualSignature == expectedSignature)
{ {
// Payment is verified, process accordingly e.g. update tenant payment details _logger.LogInfo("Payment signature verified successfully for OrderId: {OrderId}", request.OrderId);
return Ok(new { status = "success" });
// 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<object>.SuccessResponse(responseData, "Payment verified successfully", 200));
} }
else else
{ {
return BadRequest(new { status = "failure", message = "Invalid signature" }); _logger.LogWarning("Payment signature verification failed for OrderId: {OrderId}", request.OrderId);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid signature - Payment verification failed", 400));
} }
} }
catch (Exception ex)
{
_logger.LogError(ex, "Error during payment verification for OrderId: {OrderId}", request.OrderId ?? "");
return StatusCode(500, ApiResponse<object>.ErrorResponse("An error occurred during payment verification", 500));
}
}
} }
} }

View File

@ -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<string, object> options = new Dictionary<string, object>
{
{ "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;
}
}
}

View File

@ -183,6 +183,7 @@ builder.Services.AddScoped<IExpensesService, ExpensesService>();
builder.Services.AddScoped<IMasterService, MasterService>(); builder.Services.AddScoped<IMasterService, MasterService>();
builder.Services.AddScoped<IDirectoryService, DirectoryService>(); builder.Services.AddScoped<IDirectoryService, DirectoryService>();
builder.Services.AddScoped<IFirebaseService, FirebaseService>(); builder.Services.AddScoped<IFirebaseService, FirebaseService>();
builder.Services.AddScoped<IRazorpayService, RazorpayService>();
#endregion #endregion
#region Helpers #region Helpers
@ -191,7 +192,6 @@ builder.Services.AddScoped<UserHelper>();
builder.Services.AddScoped<RolesHelper>(); builder.Services.AddScoped<RolesHelper>();
builder.Services.AddScoped<EmployeeHelper>(); builder.Services.AddScoped<EmployeeHelper>();
builder.Services.AddScoped<ReportHelper>(); builder.Services.AddScoped<ReportHelper>();
builder.Services.AddScoped<PaymentHelper>();
builder.Services.AddScoped<CacheUpdateHelper>(); builder.Services.AddScoped<CacheUpdateHelper>();
builder.Services.AddScoped<FeatureDetailsHelper>(); builder.Services.AddScoped<FeatureDetailsHelper>();
builder.Services.AddScoped<UtilityMongoDBHelper>(); builder.Services.AddScoped<UtilityMongoDBHelper>();

View File

@ -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<string, object> options = new Dictionary<string, object>
{
{ "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;
}
/// <summary>
/// Fetch complete payment details from Razorpay including card details
/// </summary>
/// <param name="paymentId">Razorpay Payment ID</param>
/// <returns>Complete payment details with card information</returns>
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;
}
}
/// <summary>
/// Extract card details from payment
/// </summary>
private CardDetails? ExtractCardDetails(Payment payment)
{
try
{
var cardObj = payment.Attributes["card"];
if (cardObj == null) return null;
string json = JsonConvert.SerializeObject(cardObj);
Dictionary<string, object>? cardDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(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;
}
}
/// <summary>
/// Extract bank details from payment
/// </summary>
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;
}
}
/// <summary>
/// Extract UPI details from payment
/// </summary>
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;
}
}
/// <summary>
/// Extract wallet details from payment
/// </summary>
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;
}
}
/// <summary>
/// Extract customer name from payment object
/// </summary>
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<string, object>;
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<string, object>? dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(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";
}
}
/// <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,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);
}
}

View File

@ -54,5 +54,9 @@
"Razorpay": { "Razorpay": {
"Key": "rzp_test_RXCzgEcXucbuAi", "Key": "rzp_test_RXCzgEcXucbuAi",
"Secret": "YNAVBXxRsDg8Oat4M1C3m09W" "Secret": "YNAVBXxRsDg8Oat4M1C3m09W"
},
"Encryption": {
"PaymentKey": "4oZV1Za+zqD0CgTvRrj5zUVQeMxok5MSTz6c3wsECdM=",
"CollectionKey": "9bVvYrbL1uB+v6TjWRsJ8N8VFI8rE7e7hVhVSKg3JZU="
} }
} }