Razorpay Payment Integration in Java: Complete Guide

Razorpay is a popular payment gateway in India that supports UPI, net banking, credit/debit cards, wallets, and more. This guide covers complete integration with Java/Spring Boot.


1. Project Setup and Dependencies

Maven Dependencies (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>razorpay-java-integration</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>2.7.14</spring.boot.version>
<razorpay.version>2.8.1</razorpay.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- Razorpay Java SDK -->
<dependency>
<groupId>com.razorpay</groupId>
<artifactId>razorpay-java</artifactId>
<version>${razorpay.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok (Optional) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring.boot.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
</plugins>
</build>
</project>

2. Configuration Classes

Application Properties (application.yml)

# Razorpay Configuration
razorpay:
key-id: ${RAZORPAY_KEY_ID:rzp_test_xxxxxxxxxxxx}
key-secret: ${RAZORPAY_KEY_SECRET:}
webhook-secret: ${RAZORPAY_WEBHOOK_SECRET:}
timeout: 30000
max-retries: 3
# Server Configuration
server:
port: 8080
servlet:
context-path: /api
# Database
spring:
datasource:
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
username: sa
password: 
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
show-sql: true
h2:
console:
enabled: true
path: /h2-console
# Logging
logging:
level:
com.razorpay: DEBUG
com.example.razorpay: DEBUG

Razorpay Configuration Class

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "razorpay")
public class RazorpayConfig {
private String keyId;
private String keySecret;
private String webhookSecret;
private int timeout = 30000;
private int maxRetries = 3;
}

3. Database Entities

Payment Entity

import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "payments")
@Data
public class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "razorpay_order_id", unique = true)
private String razorpayOrderId;
@Column(name = "razorpay_payment_id", unique = true)
private String razorpayPaymentId;
@Column(name = "razorpay_signature")
private String razorpaySignature;
@Column(name = "amount", precision = 10, scale = 2)
private BigDecimal amount;
@Column(name = "currency")
private String currency = "INR";
@Column(name = "status")
@Enumerated(EnumType.STRING)
private PaymentStatus status;
@Column(name = "customer_name")
private String customerName;
@Column(name = "customer_email")
private String customerEmail;
@Column(name = "customer_phone")
private String customerPhone;
@Column(name = "description")
private String description;
@Column(name = "payment_method")
private String paymentMethod;
@Column(name = "bank")
private String bank;
@Column(name = "wallet")
private String wallet;
@Column(name = "vpa")
private String vpa; // For UPI payments
@Column(name = "card_id")
private String cardId;
@Column(name = "invoice_id")
private String invoiceId;
@Column(name = "refund_status")
private String refundStatus;
@Column(name = "refund_id")
private String refundId;
@Column(name = "error_code")
private String errorCode;
@Column(name = "error_description")
private String errorDescription;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
public enum PaymentStatus {
CREATED, ATTEMPTED, PAID, FAILED, CANCELLED, REFUNDED, PARTIALLY_REFUNDED
}
}

Refund Entity

import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "refunds")
@Data
public class Refund {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "razorpay_refund_id", unique = true)
private String razorpayRefundId;
@Column(name = "razorpay_payment_id")
private String razorpayPaymentId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "payment_id")
private Payment payment;
@Column(name = "amount", precision = 10, scale = 2)
private BigDecimal amount;
@Column(name = "currency")
private String currency = "INR";
@Column(name = "status")
private String status;
@Column(name = "speed_processed")
private String speedProcessed;
@Column(name = "speed_requested")
private String speedRequested;
@Column(name = "notes")
private String notes;
@Column(name = "receipt")
private String receipt;
@Column(name = "created_at")
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
}

4. Data Transfer Objects (DTOs)

Payment Request DTO

import lombok.Data;
import javax.validation.constraints.*;
import java.math.BigDecimal;
@Data
public class PaymentRequestDTO {
@NotNull(message = "Amount is required")
@DecimalMin(value = "1.00", message = "Amount must be at least ₹1.00")
private BigDecimal amount;
@NotBlank(message = "Currency is required")
private String currency = "INR";
@NotBlank(message = "Customer email is required")
@Email(message = "Invalid email format")
private String customerEmail;
@NotBlank(message = "Customer name is required")
private String customerName;
private String customerPhone;
private String description;
private String notes; // JSON string for additional data
@AssertTrue(message = "Must accept terms and conditions")
private Boolean acceptTerms = false;
// UPI Specific
private String upiId;
// Card Specific
private String cardNumber;
private String cardHolderName;
private String cardExpiryMonth;
private String cardExpiryYear;
private String cardCvv;
// Wallet Specific
private String wallet;
}

Payment Response DTO

import lombok.Data;
import java.math.BigDecimal;
@Data
public class PaymentResponseDTO {
private String orderId;
private String paymentId;
private BigDecimal amount;
private String currency;
private String status;
private String description;
private String customerEmail;
private String razorpaySignature;
private String errorCode;
private String errorDescription;
private String paymentMethod;
private String bank;
private String wallet;
private String vpa;
private String cardId;
private String createdAt;
// For order creation
private String razorpayKeyId;
private String orderCreationResponse;
}

Webhook DTO

import lombok.Data;
import java.util.Map;
@Data
public class WebhookDTO {
private String entity;
private String event;
private Map<String, Object> payload;
private Long createdAt;
}

5. Razorpay Service Layer

Main Razorpay Service

import com.razorpay.*;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
@Slf4j
@Service
public class RazorpayService {
private final RazorpayClient razorpayClient;
private final RazorpayConfig config;
@Autowired
public RazorpayService(RazorpayConfig config) throws RazorpayException {
this.config = config;
this.razorpayClient = new RazorpayClient(config.getKeyId(), config.getKeySecret());
}
/**
* Create a Razorpay Order
*/
public JSONObject createOrder(PaymentRequestDTO paymentRequest) throws RazorpayException {
try {
JSONObject orderRequest = new JSONObject();
// Convert amount to paise (Indian currency smallest unit)
BigDecimal amountInRupees = paymentRequest.getAmount();
int amountInPaise = amountInRupees.multiply(new BigDecimal("100")).intValue();
orderRequest.put("amount", amountInPaise);
orderRequest.put("currency", paymentRequest.getCurrency());
orderRequest.put("receipt", "receipt_" + System.currentTimeMillis());
orderRequest.put("payment_capture", 1); // Auto capture payment
// Add notes if provided
if (paymentRequest.getNotes() != null && !paymentRequest.getNotes().isEmpty()) {
JSONObject notes = new JSONObject(paymentRequest.getNotes());
orderRequest.put("notes", notes);
}
// Customer details
JSONObject customer = new JSONObject();
customer.put("name", paymentRequest.getCustomerName());
customer.put("email", paymentRequest.getCustomerEmail());
customer.put("contact", paymentRequest.getCustomerPhone());
orderRequest.put("customer", customer);
// Notify customer
orderRequest.put("notify", JSONObject.NULL);
log.info("Creating Razorpay order: {}", orderRequest.toString());
Order order = razorpayClient.orders.create(orderRequest);
log.info("Order created successfully: {}", order.toString());
return new JSONObject(order.toString());
} catch (RazorpayException e) {
log.error("Error creating Razorpay order: {}", e.getMessage(), e);
throw e;
}
}
/**
* Verify Payment Signature
*/
public boolean verifyPaymentSignature(String orderId, String paymentId, String signature) {
try {
String generatedSignature = Utils.generateSignature(orderId + "|" + paymentId, config.getKeySecret());
return generatedSignature.equals(signature);
} catch (Exception e) {
log.error("Error verifying payment signature: {}", e.getMessage(), e);
return false;
}
}
/**
* Verify Webhook Signature
*/
public boolean verifyWebhookSignature(String payload, String signature) {
try {
return Utils.verifyWebhookSignature(payload, signature, config.getWebhookSecret());
} catch (Exception e) {
log.error("Error verifying webhook signature: {}", e.getMessage(), e);
return false;
}
}
/**
* Capture Payment
*/
public JSONObject capturePayment(String paymentId, BigDecimal amount) throws RazorpayException {
try {
int amountInPaise = amount.multiply(new BigDecimal("100")).intValue();
JSONObject captureRequest = new JSONObject();
captureRequest.put("amount", amountInPaise);
captureRequest.put("currency", "INR");
Payment payment = razorpayClient.payments.capture(paymentId, captureRequest);
return new JSONObject(payment.toString());
} catch (RazorpayException e) {
log.error("Error capturing payment {}: {}", paymentId, e.getMessage(), e);
throw e;
}
}
/**
* Fetch Payment Details
*/
public JSONObject getPaymentDetails(String paymentId) throws RazorpayException {
try {
Payment payment = razorpayClient.payments.fetch(paymentId);
return new JSONObject(payment.toString());
} catch (RazorpayException e) {
log.error("Error fetching payment {}: {}", paymentId, e.getMessage(), e);
throw e;
}
}
/**
* Fetch Order Details
*/
public JSONObject getOrderDetails(String orderId) throws RazorpayException {
try {
Order order = razorpayClient.orders.fetch(orderId);
return new JSONObject(order.toString());
} catch (RazorpayException e) {
log.error("Error fetching order {}: {}", orderId, e.getMessage(), e);
throw e;
}
}
/**
* Create Refund
*/
public JSONObject createRefund(String paymentId, BigDecimal amount, String notes) throws RazorpayException {
try {
JSONObject refundRequest = new JSONObject();
if (amount != null) {
int amountInPaise = amount.multiply(new BigDecimal("100")).intValue();
refundRequest.put("amount", amountInPaise);
}
if (notes != null && !notes.isEmpty()) {
JSONObject notesJson = new JSONObject();
notesJson.put("reason", notes);
refundRequest.put("notes", notesJson);
}
Refund refund = razorpayClient.refunds.create(paymentId, refundRequest);
return new JSONObject(refund.toString());
} catch (RazorpayException e) {
log.error("Error creating refund for payment {}: {}", paymentId, e.getMessage(), e);
throw e;
}
}
/**
* Fetch Refund Details
*/
public JSONObject getRefundDetails(String refundId) throws RazorpayException {
try {
Refund refund = razorpayClient.refunds.fetch(refundId);
return new JSONObject(refund.toString());
} catch (RazorpayException e) {
log.error("Error fetching refund {}: {}", refundId, e.getMessage(), e);
throw e;
}
}
/**
* Create Payment Link
*/
public JSONObject createPaymentLink(PaymentRequestDTO paymentRequest) throws RazorpayException {
try {
JSONObject linkRequest = new JSONObject();
int amountInPaise = paymentRequest.getAmount().multiply(new BigDecimal("100")).intValue();
linkRequest.put("amount", amountInPaise);
linkRequest.put("currency", paymentRequest.getCurrency());
linkRequest.put("accept_partial", false);
linkRequest.put("first_min_partial_amount", amountInPaise);
linkRequest.put("description", paymentRequest.getDescription());
linkRequest.put("customer", new JSONObject()
.put("name", paymentRequest.getCustomerName())
.put("email", paymentRequest.getCustomerEmail())
.put("contact", paymentRequest.getCustomerPhone()));
linkRequest.put("notify", new JSONObject()
.put("sms", true)
.put("email", true));
linkRequest.put("reminder_enable", true);
linkRequest.put("callback_url", "https://your-domain.com/payment/callback");
linkRequest.put("callback_method", "get");
com.razorpay.PaymentLink paymentLink = razorpayClient.paymentLink.create(linkRequest);
return new JSONObject(paymentLink.toString());
} catch (RazorpayException e) {
log.error("Error creating payment link: {}", e.getMessage(), e);
throw e;
}
}
/**
* Create UPI Intent
*/
public JSONObject createUpiIntent(PaymentRequestDTO paymentRequest) throws RazorpayException {
try {
JSONObject intentRequest = new JSONObject();
int amountInPaise = paymentRequest.getAmount().multiply(new BigDecimal("100")).intValue();
intentRequest.put("amount", amountInPaise);
intentRequest.put("currency", paymentRequest.getCurrency());
intentRequest.put("payment_capture", 1);
intentRequest.put("method", "upi");
if (paymentRequest.getUpiId() != null && !paymentRequest.getUpiId().isEmpty()) {
JSONObject upi = new JSONObject();
upi.put("vpa", paymentRequest.getUpiId());
intentRequest.put("upi", upi);
}
Order order = razorpayClient.orders.create(intentRequest);
return new JSONObject(order.toString());
} catch (RazorpayException e) {
log.error("Error creating UPI intent: {}", e.getMessage(), e);
throw e;
}
}
}

Payment Processing Service

import com.razorpay.RazorpayException;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Optional;
@Slf4j
@Service
public class PaymentProcessingService {
@Autowired
private RazorpayService razorpayService;
@Autowired
private PaymentRepository paymentRepository;
@Autowired
private RefundRepository refundRepository;
/**
* Process Payment Creation
*/
@Transactional
public PaymentResponseDTO processPaymentCreation(PaymentRequestDTO paymentRequest) throws RazorpayException {
try {
// Create order in Razorpay
JSONObject orderResponse = razorpayService.createOrder(paymentRequest);
// Save payment record
Payment payment = new Payment();
payment.setRazorpayOrderId(orderResponse.getString("id"));
payment.setAmount(paymentRequest.getAmount());
payment.setCurrency(paymentRequest.getCurrency());
payment.setCustomerName(paymentRequest.getCustomerName());
payment.setCustomerEmail(paymentRequest.getCustomerEmail());
payment.setCustomerPhone(paymentRequest.getCustomerPhone());
payment.setDescription(paymentRequest.getDescription());
payment.setStatus(Payment.PaymentStatus.CREATED);
payment = paymentRepository.save(payment);
// Prepare response
PaymentResponseDTO response = new PaymentResponseDTO();
response.setOrderId(orderResponse.getString("id"));
response.setAmount(paymentRequest.getAmount());
response.setCurrency(paymentRequest.getCurrency());
response.setStatus("created");
response.setRazorpayKeyId(razorpayService.getConfig().getKeyId());
response.setOrderCreationResponse(orderResponse.toString());
log.info("Payment process initiated for order: {}", orderResponse.getString("id"));
return response;
} catch (Exception e) {
log.error("Error processing payment creation: {}", e.getMessage(), e);
throw e;
}
}
/**
* Process Payment Verification
*/
@Transactional
public PaymentResponseDTO processPaymentVerification(String orderId, String paymentId, String signature) {
try {
// Verify payment signature
boolean isValidSignature = razorpayService.verifyPaymentSignature(orderId, paymentId, signature);
if (!isValidSignature) {
log.warn("Invalid payment signature for order: {}, payment: {}", orderId, paymentId);
throw new RuntimeException("Invalid payment signature");
}
// Fetch payment details from Razorpay
JSONObject paymentDetails = razorpayService.getPaymentDetails(paymentId);
// Update payment record
Optional<Payment> paymentOpt = paymentRepository.findByRazorpayOrderId(orderId);
if (paymentOpt.isEmpty()) {
throw new RuntimeException("Payment record not found for order: " + orderId);
}
Payment payment = paymentOpt.get();
payment.setRazorpayPaymentId(paymentId);
payment.setRazorpaySignature(signature);
payment.setStatus(Payment.PaymentStatus.PAID);
payment.setPaymentMethod(paymentDetails.optString("method"));
payment.setBank(paymentDetails.optString("bank"));
payment.setWallet(paymentDetails.optString("wallet"));
payment.setVpa(paymentDetails.optString("vpa"));
payment.setCardId(paymentDetails.optString("card_id"));
payment = paymentRepository.save(payment);
// Prepare response
PaymentResponseDTO response = new PaymentResponseDTO();
response.setPaymentId(paymentId);
response.setOrderId(orderId);
response.setAmount(payment.getAmount());
response.setCurrency(payment.getCurrency());
response.setStatus("paid");
response.setPaymentMethod(payment.getPaymentMethod());
response.setBank(payment.getBank());
response.setWallet(payment.getWallet());
response.setVpa(payment.getVpa());
log.info("Payment verified successfully for order: {}, payment: {}", orderId, paymentId);
return response;
} catch (Exception e) {
log.error("Error processing payment verification: {}", e.getMessage(), e);
// Update payment status to failed
paymentRepository.findByRazorpayOrderId(orderId).ifPresent(payment -> {
payment.setStatus(Payment.PaymentStatus.FAILED);
payment.setErrorCode("VERIFICATION_FAILED");
payment.setErrorDescription(e.getMessage());
paymentRepository.save(payment);
});
throw new RuntimeException("Payment verification failed: " + e.getMessage());
}
}
/**
* Process Refund
*/
@Transactional
public JSONObject processRefund(String paymentId, BigDecimal amount, String reason) throws RazorpayException {
try {
// Create refund in Razorpay
JSONObject refundResponse = razorpayService.createRefund(paymentId, amount, reason);
// Save refund record
Optional<Payment> paymentOpt = paymentRepository.findByRazorpayPaymentId(paymentId);
if (paymentOpt.isPresent()) {
Payment payment = paymentOpt.get();
Refund refund = new Refund();
refund.setRazorpayRefundId(refundResponse.getString("id"));
refund.setRazorpayPaymentId(paymentId);
refund.setPayment(payment);
refund.setAmount(new BigDecimal(refundResponse.getInt("amount")).divide(new BigDecimal("100")));
refund.setCurrency(refundResponse.getString("currency"));
refund.setStatus(refundResponse.getString("status"));
refund.setSpeedProcessed(refundResponse.optString("speed_processed"));
refund.setSpeedRequested(refundResponse.optString("speed_requested"));
refund.setNotes(reason);
refundRepository.save(refund);
// Update payment status
payment.setRefundStatus("refunded");
if (amount.compareTo(payment.getAmount()) < 0) {
payment.setStatus(Payment.PaymentStatus.PARTIALLY_REFUNDED);
} else {
payment.setStatus(Payment.PaymentStatus.REFUNDED);
}
paymentRepository.save(payment);
}
log.info("Refund processed successfully: {}", refundResponse.getString("id"));
return refundResponse;
} catch (Exception e) {
log.error("Error processing refund: {}", e.getMessage(), e);
throw e;
}
}
}

6. Repository Layer

Payment Repository

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Repository
public interface PaymentRepository extends JpaRepository<Payment, Long> {
Optional<Payment> findByRazorpayOrderId(String razorpayOrderId);
Optional<Payment> findByRazorpayPaymentId(String razorpayPaymentId);
List<Payment> findByCustomerEmail(String customerEmail);
List<Payment> findByStatus(Payment.PaymentStatus status);
List<Payment> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
@Query("SELECT p FROM Payment p WHERE p.amount >= :minAmount AND p.amount <= :maxAmount")
List<Payment> findByAmountRange(@Param("minAmount") BigDecimal minAmount, 
@Param("maxAmount") BigDecimal maxAmount);
@Query("SELECT COUNT(p) FROM Payment p WHERE p.status = :status")
Long countByStatus(@Param("status") Payment.PaymentStatus status);
@Query("SELECT SUM(p.amount) FROM Payment p WHERE p.status = 'PAID' AND p.createdAt BETWEEN :start AND :end")
BigDecimal getTotalRevenueByPeriod(@Param("start") LocalDateTime start, 
@Param("end") LocalDateTime end);
}

Refund Repository

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface RefundRepository extends JpaRepository<Refund, Long> {
Optional<Refund> findByRazorpayRefundId(String razorpayRefundId);
List<Refund> findByRazorpayPaymentId(String razorpayPaymentId);
List<Refund> findByStatus(String status);
}

7. REST Controller

Payment Controller

import com.razorpay.RazorpayException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
@Autowired
private PaymentProcessingService paymentProcessingService;
@Autowired
private RazorpayService razorpayService;
/**
* Create Payment Order
*/
@PostMapping("/create-order")
public ResponseEntity<?> createPaymentOrder(@Valid @RequestBody PaymentRequestDTO paymentRequest) {
try {
PaymentResponseDTO response = paymentProcessingService.processPaymentCreation(paymentRequest);
return ResponseEntity.ok(response);
} catch (RazorpayException e) {
log.error("Error creating payment order: {}", e.getMessage(), e);
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", true);
errorResponse.put("message", "Failed to create payment order");
errorResponse.put("error_code", e.getStatusCode());
errorResponse.put("error_description", e.getMessage());
return ResponseEntity.badRequest().body(errorResponse);
}
}
/**
* Verify Payment
*/
@PostMapping("/verify")
public ResponseEntity<?> verifyPayment(
@RequestParam String order_id,
@RequestParam String payment_id,
@RequestParam String signature) {
try {
PaymentResponseDTO response = paymentProcessingService.processPaymentVerification(order_id, payment_id, signature);
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("Error verifying payment: {}", e.getMessage(), e);
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", true);
errorResponse.put("message", "Payment verification failed");
errorResponse.put("error_description", e.getMessage());
return ResponseEntity.badRequest().body(errorResponse);
}
}
/**
* Get Payment Details
*/
@GetMapping("/{paymentId}")
public ResponseEntity<?> getPaymentDetails(@PathVariable String paymentId) {
try {
var paymentDetails = razorpayService.getPaymentDetails(paymentId);
return ResponseEntity.ok(paymentDetails.toMap());
} catch (RazorpayException e) {
log.error("Error fetching payment details: {}", e.getMessage(), e);
return ResponseEntity.notFound().build();
}
}
/**
* Create Refund
*/
@PostMapping("/{paymentId}/refund")
public ResponseEntity<?> createRefund(
@PathVariable String paymentId,
@RequestParam(required = false) BigDecimal amount,
@RequestParam(required = false) String reason) {
try {
var refundResponse = paymentProcessingService.processRefund(paymentId, amount, reason);
return ResponseEntity.ok(refundResponse.toMap());
} catch (RazorpayException e) {
log.error("Error creating refund: {}", e.getMessage(), e);
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", true);
errorResponse.put("message", "Refund creation failed");
errorResponse.put("error_code", e.getStatusCode());
errorResponse.put("error_description", e.getMessage());
return ResponseEntity.badRequest().body(errorResponse);
}
}
/**
* Create UPI Payment Intent
*/
@PostMapping("/upi/intent")
public ResponseEntity<?> createUpiIntent(@Valid @RequestBody PaymentRequestDTO paymentRequest) {
try {
var upiResponse = razorpayService.createUpiIntent(paymentRequest);
return ResponseEntity.ok(upiResponse.toMap());
} catch (RazorpayException e) {
log.error("Error creating UPI intent: {}", e.getMessage(), e);
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", true);
errorResponse.put("message", "Failed to create UPI intent");
errorResponse.put("error_code", e.getStatusCode());
errorResponse.put("error_description", e.getMessage());
return ResponseEntity.badRequest().body(errorResponse);
}
}
/**
* Create Payment Link
*/
@PostMapping("/payment-link")
public ResponseEntity<?> createPaymentLink(@Valid @RequestBody PaymentRequestDTO paymentRequest) {
try {
var paymentLinkResponse = razorpayService.createPaymentLink(paymentRequest);
return ResponseEntity.ok(paymentLinkResponse.toMap());
} catch (RazorpayException e) {
log.error("Error creating payment link: {}", e.getMessage(), e);
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", true);
errorResponse.put("message", "Failed to create payment link");
errorResponse.put("error_code", e.getStatusCode());
errorResponse.put("error_description", e.getMessage());
return ResponseEntity.badRequest().body(errorResponse);
}
}
}

Webhook Controller

import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;
@Slf4j
@RestController
@RequestMapping("/api/webhooks")
public class WebhookController {
@Autowired
private RazorpayService razorpayService;
@Autowired
private PaymentRepository paymentRepository;
/**
* Handle Razorpay Webhooks
*/
@PostMapping("/razorpay")
public ResponseEntity<?> handleRazorpayWebhook(HttpServletRequest request, @RequestBody String payload) {
try {
// Get signature from header
String razorpaySignature = request.getHeader("X-Razorpay-Signature");
if (razorpaySignature == null) {
log.warn("Missing Razorpay signature in webhook");
return ResponseEntity.badRequest().body("Missing signature");
}
// Verify webhook signature
boolean isValidSignature = razorpayService.verifyWebhookSignature(payload, razorpaySignature);
if (!isValidSignature) {
log.warn("Invalid webhook signature: {}", razorpaySignature);
return ResponseEntity.badRequest().body("Invalid signature");
}
// Parse webhook payload
JSONObject webhookData = new JSONObject(payload);
String event = webhookData.getString("event");
JSONObject payloadData = webhookData.getJSONObject("payload");
log.info("Received Razorpay webhook event: {}", event);
// Process different webhook events
switch (event) {
case "payment.captured":
handlePaymentCaptured(payloadData);
break;
case "payment.failed":
handlePaymentFailed(payloadData);
break;
case "refund.processed":
handleRefundProcessed(payloadData);
break;
case "subscription.charged":
handleSubscriptionCharged(payloadData);
break;
default:
log.info("Unhandled webhook event: {}", event);
}
return ResponseEntity.ok().body("Webhook processed successfully");
} catch (Exception e) {
log.error("Error processing webhook: {}", e.getMessage(), e);
return ResponseEntity.status(500).body("Error processing webhook");
}
}
private void handlePaymentCaptured(JSONObject payload) {
try {
JSONObject payment = payload.getJSONObject("payment").getJSONObject("entity");
String paymentId = payment.getString("id");
String orderId = payment.getString("order_id");
log.info("Payment captured: {}, Order: {}", paymentId, orderId);
// Update payment status in database
paymentRepository.findByRazorpayOrderId(orderId).ifPresent(p -> {
p.setRazorpayPaymentId(paymentId);
p.setStatus(Payment.PaymentStatus.PAID);
p.setPaymentMethod(payment.optString("method"));
p.setBank(payment.optString("bank"));
p.setWallet(payment.optString("wallet"));
p.setVpa(payment.optString("vpa"));
paymentRepository.save(p);
});
// TODO: Send confirmation email, update order status, etc.
} catch (Exception e) {
log.error("Error handling payment.captured webhook: {}", e.getMessage(), e);
}
}
private void handlePaymentFailed(JSONObject payload) {
try {
JSONObject payment = payload.getJSONObject("payment").getJSONObject("entity");
String paymentId = payment.getString("id");
String orderId = payment.getString("order_id");
String errorCode = payment.getJSONObject("error").getString("code");
String errorDescription = payment.getJSONObject("error").getString("description");
log.warn("Payment failed: {}, Order: {}, Error: {}", paymentId, orderId, errorCode);
// Update payment status in database
paymentRepository.findByRazorpayOrderId(orderId).ifPresent(p -> {
p.setRazorpayPaymentId(paymentId);
p.setStatus(Payment.PaymentStatus.FAILED);
p.setErrorCode(errorCode);
p.setErrorDescription(errorDescription);
paymentRepository.save(p);
});
} catch (Exception e) {
log.error("Error handling payment.failed webhook: {}", e.getMessage(), e);
}
}
private void handleRefundProcessed(JSONObject payload) {
try {
JSONObject refund = payload.getJSONObject("refund").getJSONObject("entity");
String refundId = refund.getString("id");
String paymentId = refund.getString("payment_id");
log.info("Refund processed: {}, Payment: {}", refundId, paymentId);
// Update refund status in database
// Implementation depends on your refund tracking logic
} catch (Exception e) {
log.error("Error handling refund.processed webhook: {}", e.getMessage(), e);
}
}
private void handleSubscriptionCharged(JSONObject payload) {
try {
JSONObject subscription = payload.getJSONObject("subscription").getJSONObject("entity");
String subscriptionId = subscription.getString("id");
log.info("Subscription charged: {}", subscriptionId);
// Handle subscription payment
// Implementation depends on your subscription logic
} catch (Exception e) {
log.error("Error handling subscription.charged webhook: {}", e.getMessage(), e);
}
}
}

8. Frontend Integration Example

HTML/JavaScript Payment Form

<!DOCTYPE html>
<html>
<head>
<title>Razorpay Payment Integration</title>
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
<style>
.payment-form {
max-width: 400px;
margin: 50px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, select {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background-color: #528FF0;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
}
button:hover {
background-color: #3a7bd5;
}
</style>
</head>
<body>
<div class="payment-form">
<h2>Make Payment</h2>
<form id="paymentForm">
<div class="form-group">
<label for="amount">Amount (₹)</label>
<input type="number" id="amount" name="amount" min="1" step="0.01" required>
</div>
<div class="form-group">
<label for="customerName">Name</label>
<input type="text" id="customerName" name="customerName" required>
</div>
<div class="form-group">
<label for="customerEmail">Email</label>
<input type="email" id="customerEmail" name="customerEmail" required>
</div>
<div class="form-group">
<label for="customerPhone">Phone</label>
<input type="tel" id="customerPhone" name="customerPhone" required>
</div>
<div class="form-group">
<label for="description">Description</label>
<input type="text" id="description" name="description">
</div>
<button type="submit">Pay Now</button>
</form>
</div>
<script>
document.getElementById('paymentForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = {
amount: document.getElementById('amount').value,
customerName: document.getElementById('customerName').value,
customerEmail: document.getElementById('customerEmail').value,
customerPhone: document.getElementById('customerPhone').value,
description: document.getElementById('description').value,
currency: 'INR',
acceptTerms: true
};
try {
// Create order on your backend
const response = await fetch('/api/payments/create-order', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});
const orderData = await response.json();
if (orderData.error) {
alert('Error: ' + orderData.error_description);
return;
}
// Razorpay checkout options
const options = {
key: orderData.razorpayKeyId, // Your Razorpay key ID
amount: orderData.amount * 100, // Amount in paise
currency: orderData.currency,
name: 'Your Company Name',
description: orderData.description,
order_id: orderData.orderId,
handler: async function(response) {
// Handle successful payment
const verificationResponse = await fetch('/api/payments/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
order_id: response.razorpay_order_id,
payment_id: response.razorpay_payment_id,
signature: response.razorpay_signature
})
});
const verificationResult = await verificationResponse.json();
if (verificationResult.error) {
alert('Payment verification failed: ' + verificationResult.error_description);
} else {
alert('Payment successful! Payment ID: ' + verificationResult.paymentId);
// Redirect to success page or update UI
}
},
prefill: {
name: formData.customerName,
email: formData.customerEmail,
contact: formData.customerPhone
},
notes: {
description: formData.description
},
theme: {
color: '#528FF0'
}
};
const razorpay = new Razorpay(options);
razorpay.open();
} catch (error) {
console.error('Error:', error);
alert('An error occurred while processing your payment.');
}
});
</script>
</body>
</html>

This comprehensive Razorpay integration provides complete payment processing capabilities for Indian businesses, including order creation, payment verification, refunds, webhook handling, and support for various payment methods like UPI, cards, and wallets.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper