Integrate Razorpay Payment Gateway with Angular and Spring Boot Application in 14 Simple Steps

In the previous article, we have deployed our Spring Boot & Angular application on Digital Ocean Kubernetes. In this article, we are gonna integrate the Razorpay payment gateway into that application.

Disclosure: Please note that some of the links in this post are referral/affiliate links. If you click on such a link and end up making a purchase, we’ll receive a credit/commission which will help us to keep the blog running. The amount you pay for the product doesn’t increase.


Razorpay is a platform that enables businesses to accept, process, and disburse payments with its product suite. It provides Standard Integration and Quick Integration to integrate the Payment Gateway. We are gonna integrate the Razorpay Standard Checkout to accept payments in our application since it provides more control over the customization of the Checkout compared to Quick Integration.

What You’ll Build

Angular Frontend

Order Page

Checkout Page

Razorpay Standard Integration

Razorpay Manual Checkout Integration

Payment Mode: UPI QR

Razorpay UPI QR Payment mode

Spring Boot Backend

We will be implementing the following REST APIs to create/update order:

  • Creating Order – Expose a POST API with mapping /order. On passing order details, it will call the Razorpay client to create a Razorpay Order. Once the Order is created, the client will return the Razorpay Order ID which will be stored in the Order table along with the User ID and the Order ID will be returned in the response.
  • Validating and Updating Order – Expose a PUT API with mapping /order. On passing the payment response containing Razorpay payment id, order Id, and signature received from Razorpay, it will validate the authenticity of the details using the signature. Upon successful validation, it will update the Order with payment id, order id, and signature in the order table. So that, the order details of the user can be tracked.

What You’ll Need

Run the following checklist before you begin the integration:

  • Create a Razorpay Account
  • Generate API Keys:
    • Go to your Razorpay Dashboard and Select Test Mode on the top right
    • To generate the keys for the Test mode, Go to Settings -> API Keys.
    • For production, you need to generate keys for the Live Mode and use them in your Spring Boot
Razorpay API Keys

Angular Client Integration

Modify index.html

  • Check if the viewport meta tag is added in your Angular index.html. If not, add the following line.
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    • Note: If this meta tag is not present, there will be overflow issues.
  • Add <script src=""></script>
<!DOCTYPE html>
<html lang="en">
    <meta charset="utf-8" />
    <base href="/" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" type="image/x-icon" href="favicon.ico" />
  <script src=""></script>

Create Order Page


This component does the following:

  • Declares a variable called Razorpay as any type.
  • Declares a variable called options with all the parameters required for the Razorpay checkout.
    • Some of the parameters are captured in the form
    • Razorpay order id is being returned in the response of the REST API
    • Since it is a demo application, the rest of the parameters are hardcoded. But in a live application, most of these values will be captured dynamically from the user.
    • Defines a handler function to create a payment.success event. So that when event is occured, the payment response can be captured and processed.
  • Binds the form data to OrderService.createOrder() method that returns an Observable object. If the order is created successfully, then,
    • It populates the order id from the response and form parameters into the options variable.
    • Creates an instance of Razorpay with the options and calls method to initiate the payment process.
    • Defines a handler function for the payment.failed event in order to log the error details and show failure reason in the page in case of payment failure. Make sure to store the payment failure details in the Order table in the database. So that the customer can retry the payment.
  • Defines a function called onPaymentSuccess and binds the payment.success event with this function using the @HostListener decorator as we already explained in the previous article.
    • Whenever a payment.success event is triggered, this function will be invoked to process the payment response.
    • This function invokes OrderService.updateOrder() function that returns an Observable object. On successful response, it will set the paymentId from the response. So that, a payment success message will be displayed along with the paymentId.
import { HostListener, Component } from '@angular/core';
import { OrderService } from '../_services/order.service';

declare var Razorpay: any;

selector: 'app-order',
templateUrl: './order.component.html',
styleUrls: ['./order.component.css']
export class OrderComponent {

	form: any = {}; 
	paymentId: string;
	error: string;
	constructor(private orderService: OrderService) {


	options = {
	"key": "",
	"amount": "", 
	"name": "Java Chinna",
	"description": "Web Development",
	"image": "",
	"handler": function (response){
		var event = new CustomEvent("payment.success", 
				detail: response,
				bubbles: true,
				cancelable: true
	"prefill": {
	"name": "",
	"email": "",
	"contact": ""
	"notes": {
	"address": ""
	"theme": {
	"color": "#3399cc"

	onSubmit(): void {
		this.paymentId = ''; 
		this.error = ''; 
		data => {
			this.options.key = data.secretKey;
			this.options.order_id = data.razorpayOrderId;
			this.options.amount = data.applicationFee; //paise =; =; =;
			var rzp1 = new Razorpay(this.options);;
			rzp1.on('payment.failed', function (response){    
				// Todo - store this information in the server
				this.error = response.error.reason;
		err => {
			this.error = err.error.message;

	@HostListener('window:payment.success', ['$event']) 
	onPaymentSuccess(event): void {
		data => {
			this.paymentId = data.message;
		err => {
			this.error = err.error.message;


This html file has a form to capture some nessasary details like customer name, email, phone and amount. It will also show the payment status once the payment is made.

<div class="col-md-12">
	<div class="card card-container">
		<form name="form" (ngSubmit)="f.form.valid && onSubmit()" #f="ngForm" novalidate>
			<div class="form-group">
				<div class="alert alert-danger" role="alert" *ngIf="error">Payment failed: {{ error }}</div>
				<div class="alert alert-success" role="alert" *ngIf="paymentId">Payment Success. Payment ID: {{ paymentId }}</div>
			<div class="form-group">
				<label for="name">Name</label> <input type="text" class="form-control" name="name" [(ngModel)]="" required minlength="3" maxlength="20" #name="ngModel" />
				<div class="alert-danger" *ngIf="f.submitted && name.invalid">
					<div *ngIf="name.errors.required">Name is required</div>
					<div *ngIf="name.errors.minlength">Name must be at least 3 characters</div>
					<div *ngIf="name.errors.maxlength">Name must be at most 20 characters</div>
			<div class="form-group">
				<label for="email">Email</label> <input type="text" class="form-control" name="email" [(ngModel)]="" required #email="ngModel" />
				<div class="alert alert-danger" role="alert" *ngIf="f.submitted && email.invalid">Email is required!</div>
			<div class="form-group">
				<label for="phone">Phone</label> <input type="number" class="form-control" name="phone" [(ngModel)]="" required minlength="10" maxlength="10"
					#phone="ngModel" />
				<div class="alert alert-danger" role="alert" *ngIf="f.submitted && phone.invalid">
					<div *ngIf="phone.errors.required">Phone is required</div>
					<div *ngIf="phone.errors.minlength || phone.errors.maxlength">Phone must be 10 digits</div>
			<div class="form-group">
				<label for="amount">Amount</label> <input type="number" class="form-control" name="amount" [(ngModel)]="form.amount" required #amount="ngModel" />
				<div class="alert alert-danger" role="alert" *ngIf="f.submitted && amount.invalid">
					<div *ngIf="amount.errors.required">Amount is required</div>
			<div class="form-group">
				<button class="btn btn-primary btn-block">Pay</button>


label {
  display: block;
  margin-top: 10px;

.card-container.card {
  max-width: 400px !important;
  padding: 40px 40px;

.card {
  background-color: #f7f7f7;
  padding: 20px 25px 30px;
  margin: 0 auto 25px;
  margin-top: 50px;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);

Create Order Service


This is a service class used to call the Order REST API for creating and updating the order.

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AppConstants } from '../common/app.constants';

const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
declare var Razorpay: any;

providedIn: 'root'
export class OrderService {

	constructor(private http: HttpClient) {

	createOrder(order): Observable<any> {
		return + 'order', {
		amount: order.amount
		}, httpOptions);
	updateOrder(order): Observable<any> {
		return this.http.put(AppConstants.API_URL + 'order', {
		razorpayOrderId: order.razorpay_order_id,
		razorpayPaymentId: order.razorpay_payment_id,
		razorpaySignature: order.razorpay_signature
		}, httpOptions);

Define Module


Import and add OrderComponent in the module declarations

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
import { BoardAdminComponent } from './board-admin/board-admin.component';
import { BoardModeratorComponent } from './board-moderator/board-moderator.component';
import { BoardUserComponent } from './board-user/board-user.component';
import { TotpComponent } from './totp/totp.component';
import { OrderComponent } from './order/order.component';

import { authInterceptorProviders } from './_helpers/auth.interceptor';

  declarations: [
  imports: [
  providers: [authInterceptorProviders],
  bootstrap: [AppComponent]
export class AppModule { }

Define Module Routing


Import and add OrderComponent in the route declarations

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { RegisterComponent } from './register/register.component';
import { LoginComponent } from './login/login.component';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
import { BoardUserComponent } from './board-user/board-user.component';
import { BoardModeratorComponent } from './board-moderator/board-moderator.component';
import { BoardAdminComponent } from './board-admin/board-admin.component';
import { TotpComponent } from './totp/totp.component';
import { OrderComponent } from './order/order.component';

const routes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: 'login', component: LoginComponent },
  { path: 'register', component: RegisterComponent },
  { path: 'profile', component: ProfileComponent },
  { path: 'user', component: BoardUserComponent },
  { path: 'mod', component: BoardModeratorComponent },
  { path: 'admin', component: BoardAdminComponent },
  { path: 'totp', component: TotpComponent },
  { path: 'order', component: OrderComponent },
  { path: '', redirectTo: 'home', pathMatch: 'full' }

  imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy', useHash: true })],
  exports: [RouterModule]
export class AppRoutingModule { }

Add Order Menu

Let’s add an order menu in the navbar and display it only if the user is logged in.


		<ul class="navbar-nav mr-auto" routerLinkActive="active">
			<li class="nav-item"><a href="/home" class="nav-link" routerLink="home">Home </a></li>
			<li class="nav-item" *ngIf="showAdminBoard"><a href="/admin" class="nav-link" routerLink="admin">Admin Board</a></li>
			<li class="nav-item" *ngIf="showModeratorBoard"><a href="/mod" class="nav-link" routerLink="mod">Moderator Board</a></li>
			<li class="nav-item"><a href="/user" class="nav-link" *ngIf="isLoggedIn" routerLink="user">User</a></li>
			<li class="nav-item"><a href="/order" class="nav-link" *ngIf="isLoggedIn" routerLink="order">Order</a></li>

Spring Boot Backend integration

Add Razorpay Dependency


Get the latest version from the maven repository.


Configure Razorpay API Keys


Configuration class for the Razorpay API Keys

package com.javachinna.config;

import org.springframework.stereotype.Component;

import lombok.Data;

@ConfigurationProperties(prefix = "razorpay")
public class RazorPayClientConfig {
	private String key;
	private String secret;

Create Model Classes

package com.javachinna.dto.payment;

import lombok.Data;

public class OrderRequest {
	private String customerName;
	private String email;
	private String phoneNumber;
	private String amount;

package com.javachinna.dto.payment;

import lombok.Data;

public class OrderResponse {
	private String applicationFee;
	private String razorpayOrderId;
	private String secretKey;

package com.javachinna.dto.payment;

import lombok.Data;

public class PaymentResponse {
	private String razorpayOrderId;
	private String razorpayPaymentId;
	private String razorpaySignature;

Create Utilities Class

This class is used to generate a signature with a given data and secret.

package com.javachinna.util;


import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

 * This class defines common routines for generating authentication signatures
 * for Razorpay Webhook requests.
public class Signature {
	private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
	 * Computes RFC 2104-compliant HMAC signature. * @param data The data to be
	 * signed.
	 * @param key
	 *            The signing key.
	 * @return The Base64-encoded RFC 2104-compliant HMAC signature.
	 * @throws
	 *             when signature generation fails
	public static String calculateRFC2104HMAC(String data, String secret) throws {
		String result;
		try {

			// get an hmac_sha256 key from the raw secret bytes
			SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA256_ALGORITHM);

			// get an hmac_sha256 Mac instance and initialize with the signing
			// key
			Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);

			// compute the hmac on input data bytes
			byte[] rawHmac = mac.doFinal(data.getBytes());

			// base64-encode the hmac
			result = DatatypeConverter.printHexBinary(rawHmac).toLowerCase();

		} catch (Exception e) {
			throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
		return result;

Create JPA Entity

This order entity maps to the user_order table in the database

package com.javachinna.model;


import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

 * The persistent class for the user_order database table.
@Table(name = "user_order")
public class Order implements Serializable {

	private static final long serialVersionUID = 65981149772133526L;

	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	private Long userId;

	private String razorpayPaymentId;

	private String razorpayOrderId;

	private String razorpaySignature;


Create Spring Data Repository

This repository is responsible for querying the user_order table.

package com.javachinna.repo;

import org.springframework.stereotype.Repository;

import com.javachinna.model.Order;

public interface OrderRepository extends JpaRepository<Order, Long> {

	Order findByRazorpayOrderId(String orderId);

Create Service Class

This service class is responsible for the following operations:

  • Persists the Order
  • Generates a signature with Razorpay order id retrieved from order table, Razorpay payment id, and Razorpay secret
  • Compares the generated signature with Razorpay signature.
    • If matches, updates the Order with the payment id and Signature in the order table
    • Else returns the “Payment validation failed” error message.
package com.javachinna.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.javachinna.model.Order;
import com.javachinna.repo.OrderRepository;
import com.javachinna.util.Signature;

import lombok.extern.slf4j.Slf4j;

 * @author Chinna
public class OrderService {

	private OrderRepository orderRepository;

	public Order saveOrder(final String razorpayOrderId, final Long userId) {
		Order order = new Order();

	public String validateAndUpdateOrder(final String razorpayOrderId, final String razorpayPaymentId, final String razorpaySignature, final String secret) {
		String errorMsg = null;
		try {
			Order order = orderRepository.findByRazorpayOrderId(razorpayOrderId);
			// Verify if the razorpay signature matches the generated one to
			// confirm the authenticity of the details returned
			String generatedSignature = Signature.calculateRFC2104HMAC(order.getRazorpayOrderId() + "|" + razorpayPaymentId, secret);
			if (generatedSignature.equals(razorpaySignature)) {
			} else {
				errorMsg = "Payment validation failed: Signature doesn't match";
		} catch (Exception e) {
			log.error("Payment validation failed", e);
			errorMsg = e.getMessage();
		return errorMsg;

Create REST Controller


Exposes REST API for creating an order with Razorpay as well as in the database and updating the order.

package com.javachinna.controller;

import java.math.BigDecimal;
import java.math.RoundingMode;

import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.javachinna.config.CurrentUser;
import com.javachinna.config.RazorPayClientConfig;
import com.javachinna.dto.ApiResponse;
import com.javachinna.dto.LocalUser;
import com.javachinna.dto.payment.OrderRequest;
import com.javachinna.dto.payment.OrderResponse;
import com.javachinna.dto.payment.PaymentResponse;
import com.javachinna.service.OrderService;
import com.razorpay.Order;
import com.razorpay.RazorpayClient;
import com.razorpay.RazorpayException;

import lombok.extern.slf4j.Slf4j;

 * @author Chinna
public class OrderController {

	private RazorpayClient client;

	private RazorPayClientConfig razorPayClientConfig;

	private OrderService orderService;

	public OrderController(RazorPayClientConfig razorpayClientConfig) throws RazorpayException {
		this.razorPayClientConfig = razorpayClientConfig;
		this.client = new RazorpayClient(razorpayClientConfig.getKey(), razorpayClientConfig.getSecret());

	public ResponseEntity<?> createOrder(@RequestBody OrderRequest orderRequest, @CurrentUser LocalUser user) {
		OrderResponse razorPay = null;
		try {
			// The transaction amount is expressed in the currency subunit, such
			// as paise (in case of INR)
			String amountInPaise = convertRupeeToPaise(orderRequest.getAmount());
			// Create an order in RazorPay and get the order id
			Order order = createRazorPayOrder(amountInPaise);
			razorPay = getOrderResponse((String) order.get("id"), amountInPaise);
			// Save order in the database
			orderService.saveOrder(razorPay.getRazorpayOrderId(), user.getUser().getId());
		} catch (RazorpayException e) {
			log.error("Exception while create payment order", e);
			return new ResponseEntity<>(new ApiResponse(false, "Error while create payment order: " + e.getMessage()), HttpStatus.EXPECTATION_FAILED);
		return ResponseEntity.ok(razorPay);

	public ResponseEntity<?> updateOrder(@RequestBody PaymentResponse paymentResponse) {
		String errorMsg = orderService.validateAndUpdateOrder(paymentResponse.getRazorpayOrderId(), paymentResponse.getRazorpayPaymentId(), paymentResponse.getRazorpaySignature(),
		if (errorMsg != null) {
			return new ResponseEntity<>(new ApiResponse(false, errorMsg), HttpStatus.BAD_REQUEST);
		return ResponseEntity.ok(new ApiResponse(true, paymentResponse.getRazorpayPaymentId()));

	private OrderResponse getOrderResponse(String orderId, String amountInPaise) {
		OrderResponse razorPay = new OrderResponse();
		return razorPay;

	private Order createRazorPayOrder(String amount) throws RazorpayException {
		JSONObject options = new JSONObject();
		options.put("amount", amount);
		options.put("currency", "INR");
		options.put("receipt", "txn_123456");
		return client.Orders.create(options);

	private String convertRupeeToPaise(String paise) {
		BigDecimal b = new BigDecimal(paise);
		BigDecimal value = b.multiply(new BigDecimal("100"));
		return value.setScale(0, RoundingMode.UP).toString();

Run Spring Boot App with Maven

You can run the application using mvn clean spring-boot:run and the REST API services can be accessed via http://localhost:8080

Run the Angular App

You can run this App with the below command and hit the URL http://localhost:8081/ in browser

ng serve --port 8081


Source Code


That’s all folks. In this article, we have integrated Razorpay payment gateway with our Spring Boot Angular application.

Thank you for reading.

Leave a Reply