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.
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.javapublic 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.javapublic 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
After entering the details, hit submit button and you will be redirected to PayU payment page.
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.
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.