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