Spring MVC PayuMoney Integration

author-imageBy Dhiraj, 04 August,2018   0K

In this article, we will be discussing about payumoney payment gateway integration with spring mvc app. To make our spring MVC configuration simple, we will be generating a spring boot application. For client side, we will be using Thymeleaf along with AngularJS 1 to bind our model object with html.

To get started with Payu, first we need to register on the Payu portal to generate a payment key and salt. This payment key and salt will be used to generate hash using SHA-512 hash function while making payment request to Payu. The payment flow which we will be developing here has 3 parts.

  • First, take the user input for product Info, name, email, phone and amount to be paid. Other input field such as success URL, failure URL, Payment Key, Hash, transaction Id will be hidden in the HTML form.

  • Next, create an entry of this user input in our DB. Here, we are using Mysql DB. With these user input, create a unique transaction Id and generate a hash in the server side.

  • Next, submit the form to //test.payu.in/_payment where user will be asked to enter all the card details and on the successful payment, user will be redirected to a success URL that we had provided during form post to Payu. This will be a POST call from payu with a mihpayid. This will be generated by Payu.

  • Update the transaction as success in our system and redirect user to payment success page.

  • On payment failure, we can redirect user to transaction failed page.

For a quick integration guide, you can visit this PayU integration Dev guide.

Project Structure

Following is the project structure of the spring boot app that we will be building.

spring-mvc-project-strcture

Maven Dependency

pom.xml
<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
		</dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.3.7</version>
        </dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

Server Side Implementation

Let us first define our controller class. The method viewPaymentPage() will render the payment form where user will provide the details of the payment such as name, email, phone and amount. Similarly, once user clicks on the confirm button, proceedPayment() methd will be excuted. This method will save the payment details in the DB.

The method payuCallback() will be a callback method which will be executed as a callback method on the either payment success or failure.

PaymentController.java
@Controller
@RequestMapping("/payment")
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    @GetMapping
    public ModelAndView viewPaymentPage() {
        ModelAndView model = new ModelAndView();
        model.setViewName("paymentview");
        return model;
    }

    @PostMapping(path = "/payment-details")
    public @ResponseBody PaymentDetail proceedPayment(@RequestBody PaymentDetail paymentDetail){
        return paymentService.proceedPayment(paymentDetail);
    }

    @RequestMapping(path = "/payment-response", method = RequestMethod.POST)
    public @ResponseBody String payuCallback(@RequestParam String mihpayid, @RequestParam String status, @RequestParam PaymentMode mode, @RequestParam String txnid, @RequestParam String hash){
        PaymentCallback paymentCallback = new PaymentCallback();
        paymentCallback.setMihpayid(mihpayid);
        paymentCallback.setTxnid(txnid);
        paymentCallback.setMode(mode);
        paymentCallback.setHash(hash);
        paymentCallback.setStatus(status);
        return paymentService.payuCallback(paymentCallback);
    }
}

Following is the model class to map user input in server side.

PaymentDetail.java
public class PaymentDetail {

    private String email;
    private String name;
    private String phone;
    private String productInfo;
    private String amount;
    private String txnId;
    private String hash;
    private String sUrl;
    private String fUrl;
    private String key;
	
	//setters and getters

Similarly, following is the PaymentCallback model class.

PaymentCallback.java
public class PaymentCallback {

    private String txnid;
    private String mihpayid;
    private PaymentMode mode;
    private String status;
    private String hash;
	
	//setters and getters

Now, let us define our service class. Here, proceedPayment() will internally call savePaymentDetail() to save the payment details in the DB and it calls the util method to populate payment related information such as key, hash and other details. Similarly, payuCallback() will check the status and based on that it will update the transaction status in DB.

PaymentServiceImpl.java
@Service
public class PaymentServiceImpl implements PaymentService {

    @Autowired
    private PaymentRepo paymentRepository;

    @Override
    public PaymentDetail proceedPayment(PaymentDetail paymentDetail) {
        PaymentUtil paymentUtil = new PaymentUtil();
        paymentDetail = paymentUtil.populatePaymentDetail(paymentDetail);
        savePaymentDetail(paymentDetail);
        return paymentDetail;
    }

    @Override
    public String payuCallback(PaymentCallback paymentResponse) {
        String msg = "Transaction failed.";
        Payment payment = paymentRepository.findByTxnId(paymentResponse.getTxnid());
        if(payment != null) {
            //TODO validate the hash
            PaymentStatus paymentStatus = null;
            if(paymentResponse.getStatus().equals("failure")){
                paymentStatus = PaymentStatus.Failed;
            }else if(paymentResponse.getStatus().equals("success")) {
                paymentStatus = PaymentStatus.Success;
                msg = "Transaction success";
            }
            payment.setPaymentStatus(paymentStatus);
            payment.setMihpayId(paymentResponse.getMihpayid());
            payment.setMode(paymentResponse.getMode());
            paymentRepository.save(payment);
        }
        return msg;
    }

    private void savePaymentDetail(PaymentDetail paymentDetail) {
        Payment payment = new Payment();
        payment.setAmount(Double.parseDouble(paymentDetail.getAmount()));
        payment.setEmail(paymentDetail.getEmail());
        payment.setName(paymentDetail.getName());
        payment.setPaymentDate(new Date());
        payment.setPaymentStatus(PaymentStatus.Pending);
        payment.setPhone(paymentDetail.getPhone());
        payment.setProductInfo(paymentDetail.getProductInfo());
        payment.setTxnId(paymentDetail.getTxnId());
        paymentRepository.save(payment);
    }

}

Following is our entity class.

Payment.java
@Entity
@Table(name = "Payment")
public class Payment {

    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    @Column
    private String email;
    @Column
    private String name;
    @Column
    private String phone;
    @Column
    private String productInfo;
    @Column
    private Double amount;
    @Column
    @Enumerated(EnumType.STRING)
    private PaymentStatus paymentStatus;
    @Column
    @Temporal(TemporalType.DATE)
    private Date paymentDate;
    @Column
    private String txnId;
    @Column
    private String mihpayId;
    @Column
    @Enumerated(EnumType.STRING)
    private PaymentMode mode;
PaymentMode.java
public enum PaymentMode {

    NB,DC,CC
}
PaymentStatus.java
public enum PaymentStatus {

    Pending,Failed,Success
}

Following is the PaymentUtil.java that has logic to generate the hash and an unique transaction id. While generating hash we need to understand the hash sequence. The sequence is predefined by PayU and it is available on the Dev guide. We are following the same sequence here. PayU also provides provision to add user defined information but since we don't have any user defined terms here the sequence is empty. We are only using compulsory parameters here to make our example simple. Make sure you alo append the salt at the end od the sequence.

As per PayU,

hashSequence = key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||salt.
$hash = hash("sha512", $hashSequence);
Where salt is available on the PayUMoney dashboard.
PaymentUtil.java
public class PaymentUtil {

    private static final String paymentKey = "your-key";

    private static final String paymentSalt = "your-salt";

    private static final String sUrl = "http://localhost:8080/payment/payment-response";

    private static final String fUrl = "http://localhost:8080/payment/payment-response";

    public static PaymentDetail populatePaymentDetail(PaymentDetail paymentDetail){
        String hashString = "";
        Random rand = new Random();
        String randomId = Integer.toString(rand.nextInt()) + (System.currentTimeMillis() / 1000L);
        String txnId = "Dev" + hashCal("SHA-256", randomId).substring(0, 12);
        paymentDetail.setTxnId(txnId);
        String hash = "";
        //String otherPostParamSeq = "phone|surl|furl|lastname|curl|address1|address2|city|state|country|zipcode|pg";
        String hashSequence = "key|txnid|amount|productinfo|firstname|email|||||||||||";
        hashString = hashSequence.concat(paymentSalt);
        hashString = hashString.replace("key", paymentKey);
        hashString = hashString.replace("txnid", txnId);
        hashString = hashString.replace("amount", paymentDetail.getAmount());
        hashString = hashString.replace("productinfo", paymentDetail.getProductInfo());
        hashString = hashString.replace("firstname", paymentDetail.getName());
        hashString = hashString.replace("email", paymentDetail.getEmail());

        hash = hashCal("SHA-512", hashString);
        paymentDetail.setHash(hash);
        paymentDetail.setfUrl(fUrl);
        paymentDetail.setsUrl(sUrl);
        paymentDetail.setKey(paymentKey);
        return paymentDetail;
    }

    public static String hashCal(String type, String str) {
        byte[] hashseq = str.getBytes();
        StringBuffer hexString = new StringBuffer();
        try {
            MessageDigest algorithm = MessageDigest.getInstance(type);
            algorithm.reset();
            algorithm.update(hashseq);
            byte messageDigest[] = algorithm.digest();
            for (int i = 0; i < messageDigest.length; i++) {
                String hex = Integer.toHexString(0xFF & messageDigest[i]);
                if (hex.length() == 1) {
                    hexString.append("0");
                }
                hexString.append(hex);
            }

        } catch (NoSuchAlgorithmException nsae) {
        }
        return hexString.toString();
    }

}

Following is the application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/testdb
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

We have very simple implementation of repository. We have only one method that pulls transaction details based on transaction id.

PaymentRepo.java
@Repository
public interface PaymentRepo extends CrudRepository {

    Payment findByTxnId(String txnId);
}

SQL Scripts

create table payment (id integer not null auto_increment, amount double precision, email varchar(255), mihpay_id varchar(255), mode varchar(255), name varchar(255), payment_date date, payment_status varchar(255), phone varchar(255), product_info varchar(255), txn_id varchar(255), primary key (id)) engine=MyISAM;

Following is our PayumoneyIntegrationServerApplication.java that will bootstrap our spring boot app.

@SpringBootApplication
public class PayumoneyIntegrationServerApplication extends SpringBootServletInitializer {

	private static Class applicationClass = PayumoneyIntegrationServerApplication.class;

	public static void main(String[] args) {
		SpringApplication.run(PayumoneyIntegrationServerApplication.class, args);
	}

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(applicationClass);
	}
}

Client Side Implementation

Following is our paymentview.html that has a form to take user input and all the static files import. On the click of confirm button, method defined in angular controller will execute which will make an API request to push the user input transaction details in the DB. On click of the submit button, it will be submitted to //test.payu.in/_payment.

As discussed above, we have key, surl,furl, hash are hidden from user and these all are bind with angular model.

paymentview.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <link rel="stylesheet" type="text/css" href="webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
    <script type="text/javascript" src="webjars/jquery/1.11.1/jquery.min.js"></script>
    <script type="text/javascript" src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.2/angular.min.js"></script>
    <script type="text/javascript" src="../paymentController.js"></script>
    <title>Hello Thymeleaf!</title>
</head>
<body ng-app="paymentApp" ng-controller="paymentCtrl">

<div class="col-md-6">
    <h2>Payment Form</h2>
<form action="https://test.payu.in/_payment" name="payuform" method="POST">
    <div class="form-group">
        <label for="productInfo">Product Name:</label>
        <input type="text" class="form-control" id="productInfo" name="productinfo" ng-model="productinfo">
    </div>
    <div class="form-group">
        <label for="firstname">Name:</label>
        <input type="text" class="form-control" id="firstname" name="firstname" ng-model="firstname">
    </div>
    <div class="form-group">
        <label for="email">Email:</label>
        <input type="email" class="form-control" id="email" name="email" ng-model="email">
    </div>
    <div class="form-group">
        <label for="email">Phone:</label>
        <input type="number" class="form-control" id="phone" name="phone" ng-model="phone">
    </div>
    <div class="form-group">
        <label for="amount">Amount:</label>
        <input type="number" class="form-control" id="amount" name="amount" ng-model="amount">
    </div>
    <textarea name="surl" id="surl" ng-model="surl" rows="2" cols="2" hidden></textarea>
    <textarea name="furl" id="furl" ng-model="furl" rows="2" cols="2" hidden></textarea>
    <textarea name="key" id="key" ng-model="key" rows="2" cols="2" hidden></textarea>
    <textarea name="hash" id="hash" ng-model="hash" rows="2" cols="2" hidden></textarea>
    <textarea name="txnid" id="txnid" ng-model="txnid" rows="2" cols="2" hidden></textarea>
    <textarea name="service_provider" id="service_provider" rows="2" cols="2" hidden></textarea>
    <button type="button" class="btn btn-default" ng-show="!showSubmitButton" ng-click="confirmPayment()">Confirm</button>
    <button type="submit" class="btn btn-danger" ng-show="showSubmitButton">Submit</button>
</form>
</div>
</body>
</html>
Following is our Angular implementation of paymentCtrl.js file. It has only one API call. On the success response, the different hidden attributes will be auto populated. You can visit my another article - Spring MVC AngularJS integration to learn about these integrations.
var App = angular.module('paymentApp', []);
App.controller('paymentCtrl',['$scope','$http','$q', function($scope, $http, $q) {

    $scope.showSubmitButton = false;
    $scope.productinfo = 'Online Course';
    $scope.firstname = '';
    $scope.email = '';
    $scope.phone = '';
    $scope.amount = '';
    $scope.surl = '';
    $scope.furl = '';
    $scope.key = '';
    $scope.hash = '';
    $scope.txnid = '';

    $scope.confirmPayment = function() {
        var url = 'http://localhost:8080/payment/payment-details';
        var data = {productInfo: $scope.productinfo, email: $scope.email, name: $scope.firstname, phone: $scope.phone, amount:$scope.amount};
        $http.post(url, data)
            .then(function (response) {
                    console.log(response.data);
                    $scope.txnid = response.data.txnId;
                    $scope.surl = response.data.sUrl;
                    $scope.furl = response.data.fUrl;
                    $scope.key = response.data.key;
                    $scope.hash = response.data.hash;
                    $scope.showSubmitButton = true;
                },
                function (errResponse) {
                    if (errResponse.status == -1) {
                        errResponse.status = 408;
                        errResponse.statusText = 'Server Timeout.';
                    }
                    alert(errResponse.status + ':' + errResponse.statusText);
                });
    }
}]);

Testing the payment Integration

Let us run PayumoneyIntegrationServerApplication.java as a java application and hit localhost:8080/payment

payu-form

After entering the details, hit submit button and you will be redirected to PayU payment page.

payu-payment-form

Once, the payment is successful, you will be redirected to http://localhost:8080/payment/payment-response and based on the status the DB will be updated.

Following is the complete response from PayU.

mihpayid=109734&mode=CC&status=failure&unmappedstatus=failed&key=gtKFFx&txnid=QDSb57a12610be90a46&amount=5000.00&cardCategory=domestic&discount=0.00&net_amount_debit=0.00&addedon=2018-07-19+20%3A47%3A24&productinfo=dataSupport&firstname=Dhiraj&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=only2dhir%40gmail.com&phone=8884377251&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=9044307d31d092e99a729710b2fedd387f31ee0d3f1dcb55dd3fc68eda1c1b6ae1d7382eb9a4c565a0c08fd6caea16544c2bc7106a68fcb602f15e3c878f67c7&field1=152536&field2=971578&field3=20180719&field4=MC&field5=794708900428&field6=45&field7=1&field8=3DS&field9=+Verification+of+Secure+Hash+Failed%3A+E700+--+Unspecified+Failure+--+Unknown+Error+--+Unable+to+be+determined--E500&payment_source=payu&PG_TYPE=AXISPG&bank_ref_num=152536&bankcode=CC&error=E500&error_Message=Bank+failed+to+authenticate+the+customer&name_on_card=Test&cardnum=401200XXXXXX1112&cardhash=This+field+is+no+longer+supported+in+postback+params.&issuing_bank=AXIS&card_type=VISAnull querystring

Also, you will receive a notification email about the status of the payment in the mailbox which yo have given on payment page.

payu-payment-success-notification

Conclusion

In this article, we discussed about integrating PayU Money integration with spring MVC and AngularJS. In the next article, we will be discussing about integrating PayU Money with Angular 5 project.

Further Reading on Spring MVC

1. Spring Boot Angular Captcha

2. Spring Boot Websocket Integration Example

3. Jwt Role Based Authorization

4. Spring Boot Jwt Auth

5. Spring Boot Security Rest Basic Authentication

If You Appreciate What We Do Here On Devglan, You Should Consider: