diff --git a/pom.xml b/pom.xml
index c30fa20..bb43cee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,6 +22,10 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
com.itextpdf
itext7-core
diff --git a/src/main/java/com/mfsys/uco/UCOURI.java b/src/main/java/com/mfsys/uco/UCOURI.java
index c2ab879..262c3e0 100644
--- a/src/main/java/com/mfsys/uco/UCOURI.java
+++ b/src/main/java/com/mfsys/uco/UCOURI.java
@@ -8,4 +8,6 @@ public interface UCOURI {
String GET_TRANSACTION_PIN = "/transactionPin";
String SUBMIT_TRANSACTION = "/submitTransaction";
String GENERATE_TRANSACTIONS_REPORT = "/generateReport";
+ String CREATE_TRAN_PIN = "/createTransactionPin";
+ String VERIFY_TRAN_PIN = "/verifyTransactionPin";
}
diff --git a/src/main/java/com/mfsys/uco/config/WebClientconfiguration.java b/src/main/java/com/mfsys/uco/config/WebClientconfiguration.java
new file mode 100644
index 0000000..121999d
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/config/WebClientconfiguration.java
@@ -0,0 +1,18 @@
+package com.mfsys.uco.config;
+
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@Configuration
+public class WebClientconfiguration {
+
+ @LoadBalanced
+ @Bean
+ public WebClient.Builder loadBalancedWebClientBuilder() {
+ return WebClient.builder();
+ }
+}
+
+
diff --git a/src/main/java/com/mfsys/uco/controller/UserController.java b/src/main/java/com/mfsys/uco/controller/UserController.java
index dfd9f1f..d7e26c8 100644
--- a/src/main/java/com/mfsys/uco/controller/UserController.java
+++ b/src/main/java/com/mfsys/uco/controller/UserController.java
@@ -6,7 +6,10 @@ import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.mfsys.uco.UCOURI;
import com.mfsys.uco.dto.*;
+import com.mfsys.uco.service.TransactionPinService;
import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.ByteArrayOutputStream;
@@ -18,6 +21,9 @@ import java.util.List;
@RestController
@RequiredArgsConstructor
public class UserController {
+
+ private final TransactionPinService transactionPinService;
+
@PostMapping(UCOURI.VIEW_BALANCE)
public ViewBalanceResponseModel viewBalance(@RequestBody ViewBalanceRequestModel viewBalanceRequestModel) {
ViewBalanceResponseModel viewBalanceResponseModel = new ViewBalanceResponseModel();
@@ -107,6 +113,27 @@ public class UserController {
return "Error generating report";
}
}
+ @PostMapping(UCOURI.CREATE_TRAN_PIN)
+ public ResponseEntity createTransactionPin(@RequestBody CreateTransactionPinRequest request) {
+ try {
+ transactionPinService.createTransactionPin(request);
+ return ResponseEntity.ok(HttpStatus.OK);
+ } catch (Exception e) {
+ return ResponseEntity.ok(HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ }
-
+ @PostMapping(UCOURI.VERIFY_TRAN_PIN)
+ public ResponseEntity> verifyPin(@RequestBody VerifyPinRequest request) {
+ try {
+ boolean isVerified = transactionPinService.verifyOTPAndSavePin(request);
+ if (isVerified) {
+ return ResponseEntity.ok("PIN verified successfully.");
+ } else {
+ return ResponseEntity.badRequest().body("PIN verification failed.");
+ }
+ } catch (Exception e) {
+ return ResponseEntity.status(500).body("Error during PIN verification: " + e.getMessage());
+ }
+ }
}
diff --git a/src/main/java/com/mfsys/uco/dto/CreateTransactionPinRequest.java b/src/main/java/com/mfsys/uco/dto/CreateTransactionPinRequest.java
new file mode 100644
index 0000000..ff517c9
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/dto/CreateTransactionPinRequest.java
@@ -0,0 +1,21 @@
+package com.mfsys.uco.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CreateTransactionPinRequest {
+ private String newTransPincode;
+ private String channelCode;
+ private String pctCstycode;
+ private String porOrgacode;
+ private String cmpCustcode;
+ private boolean isOtpRequired;
+}
+
+
diff --git a/src/main/java/com/mfsys/uco/dto/OTPRequest.java b/src/main/java/com/mfsys/uco/dto/OTPRequest.java
new file mode 100644
index 0000000..5bd06af
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/dto/OTPRequest.java
@@ -0,0 +1,21 @@
+package com.mfsys.uco.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class OTPRequest {
+ private boolean isOtpRequired;
+ private String email;
+ private String phone;
+ private String channelCode;
+ private String porOrgacode;
+ private String username;
+ private String pinType;
+ private String subject;
+}
diff --git a/src/main/java/com/mfsys/uco/dto/VerifyPinRequest.java b/src/main/java/com/mfsys/uco/dto/VerifyPinRequest.java
new file mode 100644
index 0000000..49d8211
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/dto/VerifyPinRequest.java
@@ -0,0 +1,22 @@
+package com.mfsys.uco.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class VerifyPinRequest {
+ String channelCode;
+ String pctCstycode;
+ String porOrgacode;
+ String cmpCustcode;
+ String email;
+ String obpPincode;
+ String pinType;
+}
+
+
diff --git a/src/main/java/com/mfsys/uco/model/CustomerProfile.java b/src/main/java/com/mfsys/uco/model/CustomerProfile.java
index f4fc29d..e355a10 100644
--- a/src/main/java/com/mfsys/uco/model/CustomerProfile.java
+++ b/src/main/java/com/mfsys/uco/model/CustomerProfile.java
@@ -1,10 +1,7 @@
package com.mfsys.uco.model;
import com.mfsys.comm.util.FieldNameLength;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import jakarta.persistence.Table;
+import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -16,6 +13,7 @@ import java.time.LocalDate;
@Table(name = "BN_CS_MP_CUSTOMERPROFILE")
@Data
@Builder
+@IdClass(CustomerProfileId.class)
@AllArgsConstructor
@NoArgsConstructor
public class CustomerProfile {
@@ -30,6 +28,7 @@ public class CustomerProfile {
@Column(name = "PIT_IDENVALUE", nullable = true, columnDefinition = FieldNameLength.CODE_20)
protected String pitIdenvalue;
+
@Column(name = "PIT_IDENCODE", nullable = true, columnDefinition = FieldNameLength.CODE_20)
protected String pitIdencode;
@@ -39,10 +38,21 @@ public class CustomerProfile {
@Column(name = "KYC_RENEWAL_DATE", nullable = true, columnDefinition = FieldNameLength.DATE)
protected LocalDate kycRenewalDate;
+ @Column(name = "CMP_EMAIL", nullable = true, columnDefinition = FieldNameLength.CODE_50)
+ private String cmpEmail;
+
@Column(name = "CMP_NAME", nullable = true, columnDefinition = FieldNameLength.CODE_50)
private String cmpName;
+ @Column(name = "CMP_USERNAME", nullable = true, columnDefinition = FieldNameLength.CODE_50)
+ private String cmpUserName;
+
@Column(name = "CMP_ISKYC_VERIFIED", nullable=false, columnDefinition=FieldNameLength.BOOLEAN_BIT)
protected boolean cmpIsKycVerified = false;
+ @Column(name = "CMP_TRAN_PIN", nullable=false, columnDefinition=FieldNameLength.PIN_VALUE)
+ protected String cmpTranpin;
+
+ @Column(name = "CMP_TRAN_PIN_VERIFIED", nullable=false, columnDefinition=FieldNameLength.BOOLEAN_BIT)
+ protected boolean cmpTranpinVerfied = false;
}
diff --git a/src/main/java/com/mfsys/uco/model/CustomerProfileId.java b/src/main/java/com/mfsys/uco/model/CustomerProfileId.java
new file mode 100644
index 0000000..e14213e
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/model/CustomerProfileId.java
@@ -0,0 +1,66 @@
+package com.mfsys.uco.model;
+
+import java.io.Serializable;
+
+public class CustomerProfileId implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String porOrgacode;
+ private String cmpCustcode;
+
+ public String getPorOrgacode() {
+ return porOrgacode;
+ }
+
+ public void setPorOrgacode(String porOrgacode) {
+ this.porOrgacode = porOrgacode;
+ }
+
+ public String getCmpCustcode() {
+ return cmpCustcode;
+ }
+
+ public void setCmpCustcode(String cmpCustcode) {
+ this.cmpCustcode = cmpCustcode;
+ }
+
+ public CustomerProfileId() {
+ }
+
+ public CustomerProfileId(String porOrgacode, String cmpCustcode) {
+ this.porOrgacode = porOrgacode;
+ this.cmpCustcode = cmpCustcode;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((cmpCustcode == null) ? 0 : cmpCustcode.hashCode());
+ result = prime * result + ((porOrgacode == null) ? 0 : porOrgacode.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CustomerProfileId other = (CustomerProfileId) obj;
+ if (cmpCustcode == null) {
+ if (other.cmpCustcode != null)
+ return false;
+ } else if (!cmpCustcode.equals(other.cmpCustcode))
+ return false;
+ if (porOrgacode == null) {
+ if (other.porOrgacode != null)
+ return false;
+ } else if (!porOrgacode.equals(other.porOrgacode))
+ return false;
+ return true;
+ }
+}
diff --git a/src/main/java/com/mfsys/uco/model/Pin.java b/src/main/java/com/mfsys/uco/model/Pin.java
new file mode 100644
index 0000000..e6fd970
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/model/Pin.java
@@ -0,0 +1,64 @@
+package com.mfsys.uco.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.mfsys.comm.util.FieldNameLength;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+
+import java.time.LocalDateTime;
+
+@Entity(name = "DG_PN_PIN")
+@Table(name = "DG_PN_PIN")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
+public class Pin {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "PINSERIAL", nullable = false, updatable = false, columnDefinition = FieldNameLength.BIGINT)
+ private Long pinserial;
+
+ @Column(name = "POR_ORGACODE", nullable = false, updatable = false, columnDefinition = FieldNameLength.POR_ORGACODE)
+ private String porOrgacode;
+
+ @Column(name = "username", nullable = false, updatable = false, columnDefinition = FieldNameLength.USER_NAME)
+ private String userName;
+
+ @Column(name = "CHANNEL_CODE", nullable = false, updatable = false, columnDefinition = FieldNameLength.CHANNEL_CODE)
+ private String channelCode;
+
+ @Column(name = "PINTYPE", nullable = false, updatable = false, columnDefinition = FieldNameLength.CODE_10)
+ private String pintype;
+
+ @Column(name = "PINLENGTH", nullable = false, updatable = false, columnDefinition = FieldNameLength.PIN_LENGTH)
+ private int pinlength;
+
+ @Column(name = "PINCODE", nullable = false, updatable = true, columnDefinition = FieldNameLength.PIN_VALUE)
+ private String pincode;
+
+ @Column(name = "PIN_CREATEDATE", nullable = false, updatable = true, columnDefinition = FieldNameLength.DATETIME)
+ private LocalDateTime pinCreatedate;
+
+ @Column(name = "PIN_EXPIRYDATE", nullable = false, updatable = true, columnDefinition = FieldNameLength.DATETIME)
+ private LocalDateTime pinExpirydate;
+
+ @Column(name = "PINSTATUS", nullable = false, updatable = true, columnDefinition = FieldNameLength.CODE_20)
+ private String pinstatus;
+
+ @Column(name = "PIN_VALIDATIONDATE", nullable = true, updatable = true, columnDefinition = FieldNameLength.DATETIME)
+ private LocalDateTime pinValidationdate;
+
+ @JsonIgnore
+ @Column(name = "Version", nullable = false, updatable = true, columnDefinition = FieldNameLength.INT)
+ @Version
+ private int version;
+
+}
diff --git a/src/main/java/com/mfsys/uco/repository/CustomerProfileRepository.java b/src/main/java/com/mfsys/uco/repository/CustomerProfileRepository.java
new file mode 100644
index 0000000..e3ff526
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/repository/CustomerProfileRepository.java
@@ -0,0 +1,10 @@
+package com.mfsys.uco.repository;
+
+import com.mfsys.uco.model.CustomerProfile;
+import com.mfsys.uco.model.CustomerProfileId;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface CustomerProfileRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/mfsys/uco/repository/PinRepository.java b/src/main/java/com/mfsys/uco/repository/PinRepository.java
new file mode 100644
index 0000000..43b85f8
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/repository/PinRepository.java
@@ -0,0 +1,15 @@
+package com.mfsys.uco.repository;
+
+import com.mfsys.uco.model.Pin;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface PinRepository extends JpaRepository {
+ @Query("SELECT p FROM DG_PN_PIN p WHERE p.userName = :username AND p.pinstatus = 'Unverified' AND p.pinExpirydate > CURRENT_TIMESTAMP ORDER BY p.pinserial DESC LIMIT 1")
+ Optional findLatestActiveOtpByUserName(String username);
+
+}
diff --git a/src/main/java/com/mfsys/uco/service/NotificationService.java b/src/main/java/com/mfsys/uco/service/NotificationService.java
new file mode 100644
index 0000000..692007a
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/service/NotificationService.java
@@ -0,0 +1,63 @@
+package com.mfsys.uco.service;
+
+import com.mfsys.comm.commonservices.OtpService;
+import com.mfsys.comm.constant.EurekaRegisteredMicroServicesURI;
+import com.mfsys.comm.exception.InvalidOTPException;
+import com.mfsys.uco.dto.OTPRequest;
+import com.mfsys.uco.model.Pin;
+import com.mfsys.uco.repository.PinRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+import java.util.Optional;
+
+@Component
+public class NotificationService {
+ @LoadBalanced
+ private final WebClient webClient;
+ private final OtpService otpService;
+ private final PinRepository pinRepository;
+
+ public NotificationService(WebClient.Builder webClientBuilder, OtpService otpService, PinRepository pinRepository) {
+ this.webClient = webClientBuilder.baseUrl(EurekaRegisteredMicroServicesURI.NOTIFICATION_SERVICE_LB).build();
+ this.otpService = otpService;
+ this.pinRepository = pinRepository;
+ }
+
+ public Mono sendOtp(OTPRequest otpRequest) {
+ String otp = otpRequest.isOtpRequired() ? otpService.generateOtp() : "123456";
+ Pin pin = new Pin();
+ final LocalDateTime createDate = LocalDateTime.now();
+ final LocalDateTime expiryDate = LocalDateTime.now().plusMinutes(5);
+ pin.setPinCreatedate(createDate);
+ pin.setPinExpirydate(expiryDate);
+ pin.setChannelCode(otpRequest.getChannelCode());
+ pin.setPintype(otpRequest.getPinType());
+ pin.setPincode(otp);
+ pin.setPorOrgacode(otpRequest.getPorOrgacode());
+ pin.setVersion(1);
+ pin.setPinlength(6);
+ pin.setPinstatus("Unverified");
+ pin.setUserName(otpRequest.getEmail());
+ pinRepository.save(pin);
+ webClient.post().uri("/notification/otp/email").bodyValue(Map.of("email", otpRequest.getEmail(), "subject", otpRequest.getSubject(), "otp", otp, "userName", otpRequest.getUsername())).retrieve()
+ .onStatus(status -> status.is4xxClientError() || status.is5xxServerError(), clientResponse
+ -> Mono.error(new RuntimeException("Response has error status."))).bodyToMono(String.class).block();
+ return null;
+ }
+
+ public void verifyOtp(String porOrgacode, String email, String obpPincode) {
+ Optional pin = pinRepository.findLatestActiveOtpByUserName(email);
+ if (pin.isPresent() && pin.get().getPincode().equals(obpPincode) && pin.get().getPorOrgacode().equals(porOrgacode)) {
+ pin.get().setPinstatus("VERIFIED");
+ pinRepository.save(pin.get());
+ return;
+ }
+ throw new InvalidOTPException(porOrgacode);
+ }
+}
diff --git a/src/main/java/com/mfsys/uco/service/TransactionPinService.java b/src/main/java/com/mfsys/uco/service/TransactionPinService.java
new file mode 100644
index 0000000..5c60a64
--- /dev/null
+++ b/src/main/java/com/mfsys/uco/service/TransactionPinService.java
@@ -0,0 +1,53 @@
+package com.mfsys.uco.service;
+
+import com.mfsys.uco.dto.CreateTransactionPinRequest;
+import com.mfsys.uco.dto.OTPRequest;
+import com.mfsys.uco.dto.VerifyPinRequest;
+import com.mfsys.uco.model.CustomerProfile;
+import com.mfsys.uco.model.CustomerProfileId;
+import com.mfsys.uco.repository.CustomerProfileRepository;
+import com.mfsys.uco.repository.PinRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+@RequiredArgsConstructor
+public class TransactionPinService {
+
+ private final CustomerProfileRepository customerProfileRepository;
+ private final NotificationService notificationService;
+
+ public void createTransactionPin(CreateTransactionPinRequest request) {
+ CustomerProfile profile = fetchCustomer(request.getPorOrgacode(), request.getCmpCustcode());
+ OTPRequest otpRequest = OTPRequest.builder()
+ .porOrgacode(request.getPorOrgacode())
+ .channelCode(request.getChannelCode())
+ .pinType("C_PIN")
+ .email(profile.getCmpEmail())
+ .phone(profile.getPadAdrsmobphone())
+ .isOtpRequired(request.isOtpRequired())
+ .username(profile.getCmpUserName())
+ .subject("Create Transaction Pin Verification OTP")
+ .build();
+
+ notificationService.sendOtp(otpRequest);
+ customerProfileRepository.save(profile);
+ }
+
+
+ public boolean verifyOTPAndSavePin(VerifyPinRequest request) {
+ notificationService.verifyOtp(request.getPorOrgacode(), request.getEmail(), request.getObpPincode());
+ CustomerProfile profile = fetchCustomer(request.getPorOrgacode(), request.getCmpCustcode());
+ profile.setCmpTranpinVerfied(true);
+ customerProfileRepository.save(profile);
+ return true;
+ }
+
+ public CustomerProfile fetchCustomer(String porOrgacode, String cmpCustcode){
+ CustomerProfileId profileId = new CustomerProfileId(porOrgacode,cmpCustcode);
+ return customerProfileRepository.findById(profileId)
+ .orElseThrow(() -> new IllegalArgumentException("Customer profile not found for ID: " + profileId));
+ }
+}