added login with otp and verify otp

This commit is contained in:
Lincpay Tech
2025-11-26 17:17:04 +05:30
parent 985f6eecfd
commit 6f53dedbe1
18 changed files with 595 additions and 5 deletions
@@ -0,0 +1,51 @@
package com.billpaymentservice.bbps.controller;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.billpaymentservice.bbps.dto.auth.AuthResponse;
import com.billpaymentservice.bbps.dto.auth.LoginRequest;
import com.billpaymentservice.bbps.dto.auth.OtpSendResponse;
import com.billpaymentservice.bbps.dto.auth.RegisterRequest;
import com.billpaymentservice.bbps.dto.auth.VerifyOtpRequest;
import com.billpaymentservice.bbps.service.AuthService;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping("/auth")
@CrossOrigin(origins = "*")
@Slf4j
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping(value = "/sendOtp", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<OtpSendResponse> sendOtp(@Validated @RequestBody LoginRequest request) {
log.info("Received sendOtp request for mobile: {}", request.getMobile());
OtpSendResponse resp = authService.sendOtp(request);
return ResponseEntity.ok(resp);
}
@PostMapping(value = "/verifyOtp", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<AuthResponse> verifyOtp(@Validated @RequestBody VerifyOtpRequest request) {
AuthResponse resp = authService.verifyOtp(request);
return ResponseEntity.ok(resp);
}
@PostMapping(value = "/register", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<AuthResponse> register(@Validated @RequestBody RegisterRequest request) {
AuthResponse resp = authService.registerUser(request);
return ResponseEntity.ok(resp);
}
}
@@ -54,11 +54,13 @@ public class PaymentController {
return ResponseEntity.ok(response);
}
@PostMapping(value="/callback/paymentIntiate", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResponseModel> handleBillValidateCallback(@RequestBody TransactionStatusResponse transactionStatusResponse) {
ResponseModel genralResp=paymentService.handlePaymentIntiateCallback(transactionStatusResponse);
// Return a response indicating successful receipt
return ResponseEntity.ok(genralResp);
}
}
@@ -0,0 +1,51 @@
package com.billpaymentservice.bbps.dto.auth;
import com.billpaymentservice.bbps.dto.auth.UserDto;
public class AuthResponse {
private boolean success;
private String message;
private boolean userExists;
private UserDto user;
public AuthResponse() {}
public AuthResponse(boolean success, String message, boolean userExists, UserDto user) {
this.success = success;
this.message = message;
this.userExists = userExists;
this.user = user;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isUserExists() {
return userExists;
}
public void setUserExists(boolean userExists) {
this.userExists = userExists;
}
public UserDto getUser() {
return user;
}
public void setUser(UserDto user) {
this.user = user;
}
}
@@ -0,0 +1,19 @@
package com.billpaymentservice.bbps.dto.auth;
public class LoginRequest {
private String mobile;
public LoginRequest() {}
public LoginRequest(String mobile) {
this.mobile = mobile;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
@@ -0,0 +1,29 @@
package com.billpaymentservice.bbps.dto.auth;
public class OtpSendResponse {
private boolean success;
private String message;
public OtpSendResponse() {}
public OtpSendResponse(boolean success, String message) {
this.success = success;
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@@ -0,0 +1,14 @@
package com.billpaymentservice.bbps.dto.auth;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class RegisterRequest {
private String mobile;
private String name;
private String email;
private int age;
}
@@ -0,0 +1,20 @@
package com.billpaymentservice.bbps.dto.auth;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private Long id;
private String mobile;
private String name;
private String email;
private int age;
}
@@ -0,0 +1,30 @@
package com.billpaymentservice.bbps.dto.auth;
public class VerifyOtpRequest {
private String mobile;
private String otp;
public VerifyOtpRequest() {
}
public VerifyOtpRequest(String mobile, String otp) {
this.mobile = mobile;
this.otp = otp;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getOtp() {
return otp;
}
public void setOtp(String otp) {
this.otp = otp;
}
}
@@ -0,0 +1,5 @@
package com.billpaymentservice.bbps.dto.request;
public class TransactionHistory {
}
@@ -0,0 +1,42 @@
package com.billpaymentservice.bbps.entites;
import com.billpaymentservice.bbps.audit.Auditable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "users")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity extends Auditable<String> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "mobile", nullable = false, unique = true, length = 20)
private String mobile;
@Column(name = "name", length = 150)
private String name;
@Column(name = "email", length = 150)
private String email;
@Column(name = "age")
private int age;
}
@@ -0,0 +1,13 @@
package com.billpaymentservice.bbps.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.billpaymentservice.bbps.entites.UserEntity;
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
Optional<UserEntity> findByMobile(String mobile);
}
@@ -0,0 +1,15 @@
package com.billpaymentservice.bbps.service;
import com.billpaymentservice.bbps.dto.auth.AuthResponse;
import com.billpaymentservice.bbps.dto.auth.LoginRequest;
import com.billpaymentservice.bbps.dto.auth.OtpSendResponse;
import com.billpaymentservice.bbps.dto.auth.RegisterRequest;
import com.billpaymentservice.bbps.dto.auth.VerifyOtpRequest;
import com.billpaymentservice.bbps.entites.UserEntity;
public interface AuthService {
OtpSendResponse sendOtp(LoginRequest request);
AuthResponse verifyOtp(VerifyOtpRequest request);
AuthResponse registerUser(RegisterRequest request);
UserEntity findByMobile(String mobile);
}
@@ -270,7 +270,7 @@ public class AgentServiceImpl implements AgentService {
String apiName = BBPSConstant.API_INITIATE_BILL_VALIDATE;
String reqId = fetchBillValidateRequest.getReqId();
String mid = fetchBillValidateRequest.getMid();
LOGGER.info("[{}] START | reqId={} | mid={} | billerId={}", apiName, fetchBillValidateRequest, mid,
LOGGER.info("[{}] START | reqId={} | mid={} | billerId={}", apiName, fetchBillValidateRequest.getReqId(), mid,
fetchBillValidateRequest.getBillerId());
String txnId = helper.generateTxnId(15);
String fullUrl = agentDtaUrl + BBPSConstant.BILLER_VALIDATE_URL;
@@ -395,8 +395,6 @@ public class AgentServiceImpl implements AgentService {
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
System.out.println("responseWrapper:" + responseWrapper);
mapperHelper.saveBillValidation(responseWrapper, billRequest);
LOGGER.info("Bill data saved successfully for billId: {}", billId);
return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
} catch (JsonProcessingException e) {
@@ -0,0 +1,89 @@
package com.billpaymentservice.bbps.serviceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.billpaymentservice.bbps.dto.auth.AuthResponse;
import com.billpaymentservice.bbps.dto.auth.LoginRequest;
import com.billpaymentservice.bbps.dto.auth.OtpSendResponse;
import com.billpaymentservice.bbps.dto.auth.RegisterRequest;
import com.billpaymentservice.bbps.dto.auth.UserDto;
import com.billpaymentservice.bbps.dto.auth.VerifyOtpRequest;
import com.billpaymentservice.bbps.entites.UserEntity;
import com.billpaymentservice.bbps.repository.UserRepository;
import com.billpaymentservice.bbps.service.AuthService;
import com.billpaymentservice.bbps.util.Helper;
import com.billpaymentservice.bbps.util.OtpClient;
import java.util.Optional;
@Service
public class AuthServiceImpl implements AuthService {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthServiceImpl.class);
private final UserRepository userRepository;
private final OtpClient otpClient;
private final Helper helper ;
@Autowired
public AuthServiceImpl(UserRepository userRepository, OtpClient otpClient,Helper helper) {
this.userRepository = userRepository;
this.otpClient = otpClient;
this.helper = helper;
}
@Override
public OtpSendResponse sendOtp(LoginRequest request) {
Optional<UserEntity> existing = userRepository.findByMobile(request.getMobile());
if (existing.isPresent()) {
UserEntity u = existing.get();
UserDto dto = new UserDto(u.getId(), u.getMobile(), u.getName(), u.getEmail(),u.getAge());
return null;
}
// delegate to external OTP provider
return otpClient.sendOtp(request.getMobile());
}
@Override
public AuthResponse verifyOtp(VerifyOtpRequest request) {
boolean ok = otpClient.verifyOtp(request.getMobile(), request.getOtp());
if (!ok) {
return new AuthResponse(false, "Invalid OTP", false, null);
}
Optional<UserEntity> userOpt = userRepository.findByMobile(request.getMobile());
if (userOpt.isPresent()) {
UserEntity user = userOpt.get();
UserDto dto = new UserDto(user.getId(), user.getMobile(), user.getName(), user.getEmail(),user.getAge());
return new AuthResponse(true, "Login successful", true, dto);
} else {
return new AuthResponse(true, "OTP verified; user not found - proceed to registration", false, null);
}
}
@Override
@Transactional
public AuthResponse registerUser(RegisterRequest request) {
Optional<UserEntity> existing = userRepository.findByMobile(request.getMobile());
if (existing.isPresent()) {
UserEntity u = existing.get();
UserDto dto = new UserDto(u.getId(), u.getMobile(), u.getName(), u.getEmail(),u.getAge());
return new AuthResponse(false, "User already exists", true, dto);
}
UserEntity user = helper.createNewUser(request);
user = userRepository.save(user);
UserDto dto = new UserDto(user.getId(), user.getMobile(), user.getName(), user.getEmail(),user.getAge());
return new AuthResponse(true, "Registration successful", true, dto);
}
@Override
public UserEntity findByMobile(String mobile) {
return userRepository.findByMobile(mobile).orElse(null);
}
}
@@ -8,9 +8,11 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.billpaymentservice.bbps.dto.auth.RegisterRequest;
import com.billpaymentservice.bbps.entites.BBPSMastersData;
import com.billpaymentservice.bbps.entites.InboundTraceLog;
import com.billpaymentservice.bbps.entites.OutboundTraceLog;
import com.billpaymentservice.bbps.entites.UserEntity;
import com.billpaymentservice.bbps.entites.BharatConnectMaster;
import com.billpaymentservice.bbps.exception.CustomException;
import com.billpaymentservice.bbps.repository.BBPSMasterDataRepo;
@@ -166,4 +168,14 @@ public class Helper {
return headers;
}
public UserEntity createNewUser(RegisterRequest request) {
UserEntity user = new UserEntity();
user.setMobile(request.getMobile());
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setAge(request.getAge());
return user;
}
}
@@ -0,0 +1,186 @@
package com.billpaymentservice.bbps.util;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.billpaymentservice.bbps.dto.auth.OtpSendResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class OtpClient {
private static final Logger logger = LoggerFactory.getLogger(OtpClient.class);
private final RestTemplate restTemplate = new RestTemplate();
@Value("${ekyc.mobile}")
private String sendUrl;
@Value("${ekyc.mobile.verify.otp}")
private String verifyUrl;
// New config properties for eKYC-style OTP API
@Value("${ekyc.mobile}")
private String ekycMobileUrl;
@Value("${ekyc.apikey}")
private String apiKey;
@Value("${ekyc.secreteKey}")
private String secretKey;
/**
* Calls external API to send OTP. The implementation assumes a POST with JSON {"mobile":"..."}
* Modify headers/body as per your external API. Updated to support eKYC OTP generation logic.
*/
public OtpSendResponse sendOtp(String mobile) {
try {
logger.info("Initiating OTP send process for mobile: {}", mobile);
logger.info("eKYC Mobile URL: {}", ekycMobileUrl);
logger.info("API Key: {}", apiKey != null && !apiKey.isEmpty());
logger.info("Secret Key: {}", secretKey != null && !secretKey.isEmpty());
logger.info("Send URL: {}", sendUrl);
logger.info("Verify URL: {}", verifyUrl);
logger.info("Mobile Number: {}", mobile);
// If explicit eKYC config present, use that flow
if (ekycMobileUrl != null && !ekycMobileUrl.isEmpty()) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// Build JSON body string as in user's snippet
String jsonBody = "{\"mobileNumber\":\"" + mobile + "\", \"apiKey\":\"" + apiKey
+ "\", \"secretKey\":\"" + secretKey + "\"}";
HttpEntity<String> request = new HttpEntity<>(jsonBody, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(ekycMobileUrl, HttpMethod.POST, request,
String.class);
logger.info("eKYC OTP generation API response status: {}", response.getStatusCode());
if (response.getStatusCode() == HttpStatus.OK) {
logger.info("OTP generated successfully for mobile: {}", mobile);
return new OtpSendResponse(true, "OTP generated successfully");
} else {
logger.error("Failed to call eKYC OTP generation API: {}", response.getStatusCode());
return new OtpSendResponse(false, "Failed to call OTP generation service");
}
} catch (Exception e) {
logger.error("Error calling eKYC OTP generation API: {}", e.getMessage(), e);
return new OtpSendResponse(false, "Error while generating OTP: " + e.getMessage());
}
}
// Fallback to previously configured generic sendUrl behavior
if (sendUrl == null || sendUrl.isEmpty()) {
return new OtpSendResponse(true, "(stub) OTP sent to " + mobile);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, String> body = new HashMap<>();
body.put("mobile", mobile);
HttpEntity<Map<String, String>> req = new HttpEntity<>(body, headers);
// expecting external API returns {"success":true,"message":"..."}
Map resp = restTemplate.postForObject(sendUrl, req, Map.class);
boolean success = resp != null && Boolean.TRUE.equals(resp.get("success"));
String message = resp == null ? "no response" : String.valueOf(resp.get("message"));
return new OtpSendResponse(success, message);
} catch (Exception e) {
logger.error("Unexpected error in sendOtp: {}", e.getMessage(), e);
return new OtpSendResponse(false, "Failed to call OTP send: " + e.getMessage());
}
}
/**
* Calls external API to verify OTP. Returns true if OTP valid.
*/
public boolean verifyOtp(String mobile, String otp) {
try {
// basic input validation
if (mobile == null || mobile.isEmpty() || otp == null || otp.isEmpty()) {
logger.debug("verifyOtp called with empty mobile or otp");
return false;
}
logger.info("Verifying Mobile OTP for mobile: {}", mobile);
// If an eKYC verify endpoint is configured, use it
if (verifyUrl != null && !verifyUrl.isEmpty()) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
Map<String, String> requestBody = new HashMap<>();
requestBody.put("mobileNumber", mobile);
requestBody.put("apiKey", apiKey);
requestBody.put("secretKey", secretKey);
requestBody.put("otp", otp);
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(verifyUrl, HttpMethod.POST,
requestEntity, String.class);
logger.info("Mobile OTP verification API response: status={}, body={}", response.getStatusCode(),
response.getBody());
if (response.getStatusCode() == HttpStatus.OK) {
String respBody = response.getBody();
if (respBody != null) {
// try to parse JSON and look for common verification keys
ObjectMapper mapper = new ObjectMapper();
try {
Map respMap = mapper.readValue(respBody, Map.class);
if (respMap.containsKey("verified")) {
return Boolean.TRUE.equals(respMap.get("verified"));
}
if (respMap.containsKey("success")) {
return Boolean.TRUE.equals(respMap.get("success"));
}
if (respMap.containsKey("status") && "OK".equalsIgnoreCase(String.valueOf(respMap.get("status")))) {
return true;
}
} catch (Exception e) {
logger.debug("Unable to parse verification response body to JSON: {}", e.getMessage());
// fall through to treat 200 as success
}
}
// fallback: treat HTTP 200 as successful verification
return true;
} else {
logger.warn("OTP verification endpoint returned non-OK status: {}", response.getStatusCode());
return false;
}
} catch (Exception e) {
logger.error("Error calling eKYC OTP verify API: {}", e.getMessage(), e);
return false;
}
}
// stub behaviour when no verify URL configured: accept OTP "123456"
return false;
} catch (Exception e) {
logger.error("Unexpected error in verifyOtp: {}", e.getMessage(), e);
return false;
}
}
}
@@ -20,12 +20,20 @@ swagger.server.description=Local Development Server
# DataSource
spring.datasource.url=jdbc:postgresql://localhost:5432/BBPS
spring.datasource.username=postgres
spring.datasource.password=UAT@DB_2025
#spring.datasource.password=UAT@DB_2025
#spring.datasource.password=himanshu@123
spring.datasource.password=himanshu@123
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
ekyc.apikey=key_live_823fb2b5ba984ea293b6695331f83788
ekyc.secreteKey= secret_live_b2acae6e1fd54d6faa148b08af8ae098
ekyc.mobile=https://ekyc.lincpay.in/EKYC/ekyc/mobile/generate-otp
ekyc.mobile.verify.otp=https://ekyc.lincpay.in/EKYC/ekyc/verify-mobile-otp
enc.key=lincpay
# HikariCP settings
spring.datasource.hikari.connection-timeout=10000
@@ -46,6 +46,12 @@ agent.data.url=https://bbps-staging.digiledge.in/agent/bill-payment-new
payment.data.url=https://bbps-staging.digiledge.in/couapp
complaint.data.url=https://bbps-staging.digiledge.in
ekyc.apikey=key_live_823fb2b5ba984ea293b6695331f83788
ekyc.secreteKey= secret_live_b2acae6e1fd54d6faa148b08af8ae098
ekyc.mobile=https://ekyc.lincpay.in/EKYC/ekyc/mobile/generate-otp
ekyc.mobile.verify.otp=https://ekyc.lincpay.in/EKYC/ekyc/verify-mobile-otp
# throw NoHandlerFoundException for 404s
spring.mvc.throw-exception-if-no-handler-found=true