Author SHA1 Message Date
Lincpay Tech 53641d97ea added rate limiter 2025-12-12 18:52:53 +05:30
55 changed files with 1459 additions and 1109 deletions
+11 -6
View File
@@ -45,12 +45,6 @@
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.2.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
@@ -186,7 +180,18 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Redis (Spring Data Redis + Lettuce) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Bucket4j core for local token-bucket smoothing -->
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>7.6.0</version>
</dependency>
</dependencies> </dependencies>
@@ -0,0 +1,68 @@
package com.billpaymentservice.bbps.config;
import java.time.Duration;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.core.Ordered;
import com.billpaymentservice.bbps.filter.RateLimitingFilter;
import com.billpaymentservice.bbps.ratelimit.LocalBucketManager;
@Configuration
public class RateLimiterConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterConfig.class);
@Value("${spring.redis.host:localhost}")
private String redisHost;
@Value("${spring.redis.port:6379}")
private int redisPort;
// allow comma-separated patterns, default to /api/* and /masterData/* for backward compatibility
@Value("${rate.limiter.url-pattern:/api/*,/masterData/*}")
private String urlPattern;
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration cfg = new RedisStandaloneConfiguration(redisHost, redisPort);
LettuceConnectionFactory factory = new LettuceConnectionFactory(cfg);
// optional tuning
factory.setValidateConnection(true);
return factory;
}
@Bean
public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory connectionFactory) {
StringRedisTemplate tpl = new StringRedisTemplate(connectionFactory);
tpl.setEnableTransactionSupport(false);
return tpl;
}
@Bean
public FilterRegistrationBean<RateLimitingFilter> rateLimiterFilterRegistration(StringRedisTemplate stringRedisTemplate, LocalBucketManager localBucketManager) {
RateLimitingFilter filter = new RateLimitingFilter(stringRedisTemplate, localBucketManager);
FilterRegistrationBean<RateLimitingFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(filter);
// support comma-separated patterns in property (e.g. "/api/*,/masterData/*")
String[] patterns = urlPattern.split("\\s*,\\s*");
reg.addUrlPatterns(patterns);
LOGGER.info("Registering RateLimitingFilter for patterns={}", Arrays.toString(patterns));
reg.setName("rateLimitingFilter");
// set the filter to highest precedence so it runs before other servlet filters (e.g., security chain)
reg.setOrder(Ordered.HIGHEST_PRECEDENCE);
return reg;
}
}
@@ -44,7 +44,7 @@ public class SecurityConfig {
"/webjars/**", "/webjars/**",
"/error") "/error")
.permitAll() .permitAll()
.requestMatchers("/payment/callback/startPayment","/complaints/checkTicketStatus") .requestMatchers("/payment/callback/startPayment")
.permitAll().requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .permitAll().requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated()) .anyRequest().authenticated())
.exceptionHandling(ex -> ex.authenticationEntryPoint(jwtAuthenticationEntryPoint)) .exceptionHandling(ex -> ex.authenticationEntryPoint(jwtAuthenticationEntryPoint))
@@ -46,6 +46,7 @@ public class PaymentController {
@ApiDocCommonResponses @ApiDocCommonResponses
public ResponseEntity<ResponseModel> requestBillFetch( public ResponseEntity<ResponseModel> requestBillFetch(
@RequestBody BillPaymentRequest billPaymentRequest) { @RequestBody BillPaymentRequest billPaymentRequest) {
ResponseModel response = paymentService.initiatePayment(billPaymentRequest); ResponseModel response = paymentService.initiatePayment(billPaymentRequest);
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} }
@@ -1,24 +0,0 @@
package com.billpaymentservice.bbps.dto;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ComplaintResponseDto {
private String ticketId;
private String ticketType;
private String txnNpciId;
private String status;
private String userId;
private String ticketDesp;
private String ticketDesr;
private List<ComplaintStatusDto> complaintStatusList;
private List<ComplaintHistoryDTO> historyList;
private List<ComplaintStatusDetailsDto> statusDetailsList;
private List<ViewAllComplaintDto> viewAllList;
}
@@ -1,18 +0,0 @@
package com.billpaymentservice.bbps.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ComplaintStatusDetailsDto {
private String ticketStatus;
private String ticketType;
private String txnAmount;
private String billerName;
private String ticketId;
private String totalTxnAmount;
private String billCategory;
}
@@ -1,17 +0,0 @@
package com.billpaymentservice.bbps.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ComplaintStatusDto {
private String ticketId;
private String npciTicketStatus;
private String ticketDespo;
private String createdTime;
private String npciTicketType;
}
@@ -66,12 +66,9 @@ public class TransactionDTO {
private String planType; private String planType;
// ✅ Date fields as String (for JSON compatibility) // ✅ Date fields as String (for JSON compatibility)
@JsonProperty("creationDate") private String creationDate;
private String creationDateBank; private String modifiedDate;
@JsonProperty("modifiedDate")
private String modifiedDateBank; private Long createdEpochDate;
@JsonProperty("createdEpochDate") private Long modifiedEpochDate;
private Long createdEpochDateBank;
@JsonProperty("modifiedEpochDate")
private Long modifiedEpochDateBank;
} }
@@ -22,8 +22,8 @@ public class TransactionHistoryMasterDTO {
private String billId; private String billId;
private String userType; private String userType;
private String userId; private String userId;
private String txnAmount; private Double txnAmount;
private String totalTxnAmount; private Double totalTxnAmount;
private String billerId; private String billerId;
private String billerCategory; private String billerCategory;
private String billerName; private String billerName;
@@ -33,15 +33,15 @@ public class TransactionHistoryMasterDTO {
private String quickPay; private String quickPay;
private String paymentMode; private String paymentMode;
private String agentPaymentInformation; private String agentPaymentInformation;
private String custConvFee; private Double custConvFee;
private String currency; private String currency;
private String status; private String status;
private String requestedIp; private String requestedIp;
private String customerMobNo; private String customerMobNo;
private String macAddress; private String macAddress;
private String splitPayAmount; private Double splitPayAmount;
private String couCustConvFee; private Double couCustConvFee;
private String settlementVerification; private Boolean settlementVerification;
private String fundTxnType; private String fundTxnType;
private String reason; private String reason;
private String amountTags; private String amountTags;
@@ -11,7 +11,7 @@ public class TransactionHistoryParticularDetailsDTO {
private String billerName; private String billerName;
private String totalTxnAmount; private Double totalTxnAmount;
private String bankTxnId; private String bankTxnId;
@@ -1,20 +0,0 @@
package com.billpaymentservice.bbps.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ViewAllComplaintDto {
private String ticketStatus;
private String txnAmount;
private String billerName;
private String ticketId;
private String txnNpciId;
private String ticketType;
private String createdTime;
private String billCategory;
private String totalTxnAmount;
}
@@ -3,16 +3,7 @@ package com.billpaymentservice.bbps.dto.auth;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class LoginRequest { public class LoginRequest {
@NotNull(message = "Mobile number cannot be null.") @NotNull(message = "Mobile number cannot be null.")
@@ -20,5 +11,18 @@ public class LoginRequest {
@Pattern(regexp = "^[0-9]{10}$", message = "Mobile number must contain only digits.") @Pattern(regexp = "^[0-9]{10}$", message = "Mobile number must contain only digits.")
private String mobile; 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;
}
} }
@@ -1,17 +1,29 @@
package com.billpaymentservice.bbps.dto.auth; package com.billpaymentservice.bbps.dto.auth;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class OtpSendResponse { public class OtpSendResponse {
private boolean success; private boolean success;
private String message; 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;
}
} }
@@ -6,15 +6,11 @@ import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class RegisterRequest extends BaseClass { public class RegisterRequest extends BaseClass {
@NotNull(message = "Mobile number cannot be null.") @NotNull(message = "Mobile number cannot be null.")
@@ -3,15 +3,7 @@ package com.billpaymentservice.bbps.dto.auth;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class VerifyOtpRequest { public class VerifyOtpRequest {
@NotNull(message = "Mobile number cannot be null.") @NotNull(message = "Mobile number cannot be null.")
@@ -24,5 +16,27 @@ public class VerifyOtpRequest {
@Pattern(regexp = "^[0-9]{6}$", message = "OTP must contain only 6 digits.") @Pattern(regexp = "^[0-9]{6}$", message = "OTP must contain only 6 digits.")
private String otp; 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;
}
} }
@@ -1,17 +0,0 @@
package com.billpaymentservice.bbps.dto.response;
import com.billpaymentservice.bbps.entites.PaymentStartRecord;
import com.billpaymentservice.bbps.entites.TransactionMaster;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class PaymentStartWithTransactionDto {
private PaymentStartRecord paymentStartRecord;
private TransactionMaster transactionMaster;
}
@@ -6,37 +6,29 @@ import java.time.LocalDateTime;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate; import jakarta.persistence.PreUpdate;
import java.lang.reflect.Field;
import java.util.Date;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
public class AuditListener { public class AuditListener {
@PrePersist @PrePersist
public void prePersist(Object entity) { public void prePersist(Object entity) {
Date now = new Date(); // current date + time LocalDateTime now = LocalDateTime.now();
// creation fields // Creation fields
setIfNull(entity, "createDateTime", now); setIfNull(entity, "createDateTime", now);
setIfNull(entity, "createdBy", null); setIfNull(entity, "createdBy", null); // handle manually in service if needed
setIfNull(entity, "createdEpochDate", now.getTime()); setIfNull(entity, "createdEpochDate", System.currentTimeMillis());
// default soft delete flag // Set isDeleted = false on creation
setAlways(entity, "isDeleted", Boolean.FALSE); setAlways(entity, "isDeleted", Boolean.FALSE);
} }
@PreUpdate /*
public void preUpdate(Object entity) { * @PreUpdate public void preUpdate(Object entity) { LocalDateTime now =
Date now = new Date(); * LocalDateTime.now();
*
// update fields * // Update fields setAlways(entity, "updatedDateTime", now); setIfNull(entity,
setAlways(entity, "updatedDateTime", now); * "updatedBy", null); // you can override manually in service setAlways(entity,
setIfNull(entity, "updatedBy", null); * "modifiedEpochDate", System.currentTimeMillis()); }
setAlways(entity, "modifiedEpochDate", now.getTime()); */
}
private void setIfNull(Object entity, String fieldName, Object value) { private void setIfNull(Object entity, String fieldName, Object value) {
try { try {
Field f = findField(entity.getClass(), fieldName); Field f = findField(entity.getClass(), fieldName);
@@ -45,7 +37,8 @@ public class AuditListener {
boolean accessible = f.canAccess(entity); boolean accessible = f.canAccess(entity);
f.setAccessible(true); f.setAccessible(true);
if (f.get(entity) == null) { Object currentValue = f.get(entity);
if (currentValue == null) {
f.set(entity, value); f.set(entity, value);
} }
@@ -60,7 +53,9 @@ public class AuditListener {
boolean accessible = f.canAccess(entity); boolean accessible = f.canAccess(entity);
f.setAccessible(true); f.setAccessible(true);
f.set(entity, value); f.set(entity, value);
f.setAccessible(accessible); f.setAccessible(accessible);
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
@@ -1,7 +1,6 @@
package com.billpaymentservice.bbps.entites; package com.billpaymentservice.bbps.entites;
import java.io.Serializable; import java.time.LocalDateTime;
import java.util.Date;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners; import jakarta.persistence.EntityListeners;
@@ -10,34 +9,33 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
/** /**
* Base mapped superclass for audit fields. All JPA entities should extend this * Base mapped superclass for audit fields. All JPA entities should extend this to
* to get automatic population of createdAt/updatedAt/isDeleted via * get automatic population of createdAt/updatedAt/isDeleted via AuditListener.
* AuditListener.
*/ */
@MappedSuperclass @MappedSuperclass
@EntityListeners(AuditListener.class) @EntityListeners(AuditListener.class)
@Getter @Getter
@Setter @Setter
public abstract class Auditable implements Serializable { public abstract class Auditable {
@Column(name = "create_date_time") @Column(name = "create_date_time")
private Date createDateTime; private LocalDateTime createDateTime;
@Column(name = "updated_date_time") @Column(name = "updated_date_time")
private Date updatedDateTime; private LocalDateTime updatedDateTime;
@Column(name = "create_by") @Column(name = "create_by")
private String createdBy; private LocalDateTime createdBy;
@Column(name = "updated_by") @Column(name = "updated_by")
private String updatedBy; private LocalDateTime updatedBy;
@Column(name = "created_epoch_date") @Column(name = "created_epoch_date")
private Long createdEpochDate; private Long createdEpochDate;
@Column(name = "modified_epoch_date") @Column(name = "modified_epoch_date")
private Long modifiedEpochDate; private Long modifiedEpochDate;
@Column(name = "is_deleted") @Column(name = "is_deleted")
private Boolean isDeleted = Boolean.FALSE; private Boolean isDeleted = Boolean.FALSE;
} }
@@ -5,8 +5,6 @@ import java.util.List;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
@@ -28,24 +26,16 @@ import lombok.Setter;
public class BillMaster extends Auditable { public class BillMaster extends Auditable {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "bill_id", unique = true)
private String billId; // primary key private String billId; // primary key
private String reqId; // client se aaya hua private String reqId; // client se aaya hua
private String txnId; // system-generated private String txnId; // system-generated
private String refId; private String refId;
private String billerId; private String billerId;
private String status; private String status;
private String mid;
private String actionType; private String actionType;
private String clientUserId; private String clientUserId;
private String userId; private String userId;
@Column(name = "initiator_txn_id")
private String initiatorTxnId;
@Column(name = "checker_txn_id")
private String checkerTxnId;
@OneToOne(cascade = CascadeType.ALL) @OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "biller_response_id") @JoinColumn(name = "biller_response_id")
private BillerResponse billerResponse; private BillerResponse billerResponse;
@@ -53,4 +43,6 @@ public class BillMaster extends Auditable {
private List<AdditionalInfoBill> additionalInfo; private List<AdditionalInfoBill> additionalInfo;
@OneToMany(mappedBy = "billMaster", cascade = CascadeType.ALL) @OneToMany(mappedBy = "billMaster", cascade = CascadeType.ALL)
private List<CustomerParamBill> customerParams; private List<CustomerParamBill> customerParams;
// getters and setters
} }
@@ -8,25 +8,16 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@Entity @Entity
@Table(name = "bbps_bill_validation_master") @Table(name = "bbps_bill_validation")
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class BillValidation extends Auditable { public class BillValidation extends Auditable {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "bill_id", nullable = false, length = 100)
private Long id;
@Column(name = "bill_id", unique = true)
private String billId; private String billId;
private String mid;
private String initiatorTxnId;
private String checkerTxnId;
@Column(name = "ref_id", length = 100) @Column(name = "ref_id", length = 100)
private String refId; private String refId;
@@ -64,4 +55,6 @@ public class BillValidation extends Auditable {
@Column(name = "client_user_id") @Column(name = "client_user_id")
private String clientUserId; private String clientUserId;
// Removed local audit fields: creationDate, modifiedDate, createdEpochDate, modifiedEpochDate
// They are now handled by Auditable base class
} }
@@ -2,7 +2,6 @@ package com.billpaymentservice.bbps.entites;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@@ -72,4 +71,9 @@ public class ComplainStatus extends Auditable {
@Column(name = "user_id") @Column(name = "user_id")
private String userId; private String userId;
@ManyToOne
@JoinColumn(name = "complaint_response_id")
private ComplaintResponseEntity complaintResponse;
} }
@@ -2,7 +2,6 @@ package com.billpaymentservice.bbps.entites;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@@ -47,5 +46,8 @@ public class ComplaintHistory extends Auditable {
@Column(name = "user_id") @Column(name = "user_id")
private String userId; private String userId;
@ManyToOne
@JoinColumn(name = "complaint_response_id")
private ComplaintResponseEntity complaintResponse;
} }
@@ -1,16 +1,14 @@
package com.billpaymentservice.bbps.entites; package com.billpaymentservice.bbps.entites;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.Getter; import lombok.Getter;
@@ -32,9 +30,6 @@ public class ComplaintResponseEntity extends Auditable {
@Column(name = "ref_id") @Column(name = "ref_id")
private String refId; private String refId;
@Column(name = "request_txn_id")
private String requestTxnId;
@Column(name = "txn_npci_id") @Column(name = "txn_npci_id")
private String txnNpciId; private String txnNpciId;
@@ -77,23 +72,17 @@ public class ComplaintResponseEntity extends Auditable {
@Column(name = "user_id") @Column(name = "user_id")
private String userId; private String userId;
@Column(name = "mid") @OneToMany(mappedBy = "complaintResponse", cascade = CascadeType.ALL, orphanRemoval = false)
private String mid; private List<ComplainStatus> complaintStatusList;
@OneToMany(fetch = FetchType.LAZY) @OneToMany(mappedBy = "complaintResponse", cascade = CascadeType.ALL, orphanRemoval = false)
@JoinColumn(name = "ticket_id", referencedColumnName = "ticket_id", insertable = false, updatable = false) private List<ComplaintHistory> historyList;
private Set<ComplainStatus> complaintStatusList = new HashSet<>();
@OneToMany(fetch = FetchType.LAZY) @OneToMany(mappedBy = "complaintResponse", cascade = CascadeType.ALL, orphanRemoval = false)
@JoinColumn(name = "ticket_id", referencedColumnName = "ticket_id", insertable = false, updatable = false) private List<ComplaintStatusDetails> statusDetailsList;
private Set<ComplaintHistory> historyList = new HashSet<>();
@OneToMany(fetch = FetchType.LAZY) @OneToMany(mappedBy = "complaintResponse", cascade = CascadeType.ALL, orphanRemoval = false)
@JoinColumn(name = "ticket_id", referencedColumnName = "ticket_id", insertable = false, updatable = false) private List<ViewAllComplaintTicketStatusDetails> viewAllList;
private Set<ComplaintStatusDetails> statusDetailsList = new HashSet<>();
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "ticket_id", referencedColumnName = "ticket_id", insertable = false, updatable = false)
private Set<ViewAllComplaintTicketStatusDetails> viewAllList = new HashSet<>();
} }
@@ -2,7 +2,6 @@ package com.billpaymentservice.bbps.entites;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@@ -89,6 +88,8 @@ public class ComplaintStatusDetails extends Auditable {
@Column(name = "biller_name") @Column(name = "biller_name")
private String billerName; private String billerName;
@ManyToOne
@JoinColumn(name = "complaint_response_id")
private ComplaintResponseEntity complaintResponse;
} }
@@ -1,5 +1,9 @@
package com.billpaymentservice.bbps.entites; package com.billpaymentservice.bbps.entites;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -7,15 +11,30 @@ import lombok.NoArgsConstructor;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class PaymentInitiate { @Entity
@Table(name = "bbps_payment_initiate")
// Kept as a POJO for compatibility but not annotated as @Entity so JPA won't map it public class PaymentInitiate extends Auditable {
private String reqId;
private String bankTxnId;
private String clientUserId;
private String mid;
private String txnId;
private String status;
private String userId;
@Id
@Column(name = "req_id")
private String reqId;
@Column(name = "bank_txn_Id", nullable = false)
private String bankTxnId;
@Column(name = "client_user_id")
private String clientUserId;
@Column(name = "mid")
private String mid;
@Column(name = "txn_id")
private String txnId;
@Column(name = "status")
private String status;
@Column(name = "user_id")
private String userId;
} }
@@ -11,11 +11,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.OneToOne;
import jakarta.persistence.FetchType;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Entity @Entity
@@ -26,20 +21,20 @@ import jakarta.persistence.FetchType;
public class PaymentStartRecord extends Auditable { public class PaymentStartRecord extends Auditable {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(name = "request_id", unique = true, nullable = false) @Column(name = "request_id", unique = true, nullable = false)
private String requestId; private String requestId;
@Column(name = "mid", nullable = false) @Column(name = "client_mid", nullable = false)
private String mid; private String clientMid;
@Column(name = "user_id") @Column(name = "client_user_id")
private String userId; private String clientUserId;
@Column(name = "txn_id", unique = true, nullable = false) @Column(name = "internal_txn_id", unique = true, nullable = false)
private String txnId; private String internalTxnId;
@Column(name = "order_no", unique = true, nullable = false) @Column(name = "order_no", unique = true, nullable = false)
private String orderNo; private String orderNo;
@@ -47,28 +42,42 @@ public class PaymentStartRecord extends Auditable {
@Column(name = "amount", nullable = false) @Column(name = "amount", nullable = false)
private String amount; private String amount;
@Column(name = "payment_ref_id",unique = true) @Column(name = "mid", nullable = false)
private String paymentRefId; private String mid;
@Column(name = "txn_id", unique = true, nullable = false)
private String txnId;
@Column(name = "qr_string", columnDefinition = "TEXT") @Column(name = "qr_string", columnDefinition = "TEXT")
private String qrString; private String qrString;
// `createdOn` is provided by the base `Auditable` class as a LocalDateTime field,
// so we must not redeclare it here (it caused an incompatible return-type error).
// The Auditable base class already defines `createdOn` of type LocalDateTime.
// New callback-related fields
@Column(name = "callback_status") @Column(name = "callback_status")
private String callbackStatus; private String callbackStatus;
@Column(name = "bank_response", columnDefinition = "TEXT")
private String bankResponse;
@Column(name = "signature") @Column(name = "signature")
private String signature; private String signature;
// Store original request body and full response body
@Column(name = "request_body", columnDefinition = "TEXT")
private String requestBody;
@Column(name = "response_body", columnDefinition = "TEXT")
private String responseBody;
@Column(name = "response_type") @Column(name = "response_type")
private String responseType; private String responseType; // e.g., "QR", "HTML", "JSON"
@Column(name = "txn_status") @Column(name = "txn_status")
private String txnStatus; private String txnStatus;
@Column(name = "callback_received_on") @Column(name = "callback_received_on")
private String callbackReceivedOn; private String callbackReceivedOn;
@OneToOne(mappedBy = "paymentStartRecord", fetch = FetchType.LAZY)
@JsonIgnore
private TransactionMaster transactionMaster;
} }
@@ -2,18 +2,13 @@ package com.billpaymentservice.bbps.entites;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -31,26 +26,18 @@ import lombok.ToString;
@AllArgsConstructor @AllArgsConstructor
public class TransactionMaster extends Auditable { public class TransactionMaster extends Auditable {
@Id // 👇 JSON me "txn_id" aayega, ye DB me bank_txn_id column me save hoga
@GeneratedValue(strategy = GenerationType.IDENTITY) @Id
@Column(name = "id")
private Long id;
@JsonProperty("txn_id") @JsonProperty("txn_id")
@Column(name = "bank_txn_id",unique = true) @Column(name = "bank_txn_id", nullable = false, unique = true)
private String bankTxnId; private String bankTxnId;
@Column(name = "txn_id",unique = true) // 👇 Apna internal transaction id, manually generate or system ke through
@Column(name = "txn_id")
private String txnId; private String txnId;
@Column(name = "initiator_txn_id") @Column(name = "client_user_id")
private String initiatorTxnId; private String clientUserId;
@Column(name = "checker_txn_id")
private String checkerTxnId;
@Column(name = "user_id")
private String userId;
@Column(name = "mid") @Column(name = "mid")
private String mid; private String mid;
@@ -79,14 +66,14 @@ public class TransactionMaster extends Auditable {
@Column(name = "user_type") @Column(name = "user_type")
private String userType; private String userType;
@Column(name = "bank_user_id") @Column(name = "user_id")
private String bankUserId; private String userId;
@Column(name = "txn_amount") @Column(name = "txn_amount")
private String txnAmount; private Double txnAmount;
@Column(name = "total_txn_amount") @Column(name = "total_txn_amount")
private String totalTxnAmount; private Double totalTxnAmount;
@Column(name = "biller_id") @Column(name = "biller_id")
private String billerId; private String billerId;
@@ -116,7 +103,7 @@ public class TransactionMaster extends Auditable {
private String agentPaymentInformation; private String agentPaymentInformation;
@Column(name = "cust_conv_fee") @Column(name = "cust_conv_fee")
private String custConvFee; private Double custConvFee;
@Column(name = "currency") @Column(name = "currency")
private String currency; private String currency;
@@ -134,13 +121,13 @@ public class TransactionMaster extends Auditable {
private String macAddress; private String macAddress;
@Column(name = "split_pay_amount") @Column(name = "split_pay_amount")
private String splitPayAmount; private Double splitPayAmount;
@Column(name = "cou_cust_conv_fee") @Column(name = "cou_cust_conv_fee")
private String couCustConvFee; private Double couCustConvFee;
@Column(name = "settlement_verification") @Column(name = "settlement_verification")
private String settlementVerification; private Boolean settlementVerification;
@Column(name = "fund_txn_type") @Column(name = "fund_txn_type")
private String fundTxnType; private String fundTxnType;
@@ -162,27 +149,21 @@ public class TransactionMaster extends Auditable {
@OneToMany(mappedBy = "transactionMaster", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "transactionMaster", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<CustomerParamTransaction> customerParams; private List<CustomerParamTransaction> customerParams;
// ✅ Relation with PaymentStartRecord (one-to-one)
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "payment_ref_id", referencedColumnName = "payment_ref_id", insertable = false, updatable = false)
@JsonIgnore
private PaymentStartRecord paymentStartRecord;
@Column(name = "plan_id") @Column(name = "plan_id")
private String planId; private String planId;
@Column(name = "plan_type") @Column(name = "plan_type")
private String planType; private String planType;
@Column(name = "creation_date_bank") @Column(name = "creation_date")
private String creationDateBank; private String creationDate;
@Column(name = "modified_date_bank") @Column(name = "modified_date")
private String modifiedDateBank; private String modifiedDate;
@Column(name = "created_epoch_date_bank") @Column(name = "created_epoch_date")
private Long createdEpochDateBank; private Long createdEpochDate;
@Column(name = "modified_epoch_date_bank") @Column(name = "modified_epoch_date")
private Long modifiedEpochDateBank; private Long modifiedEpochDate;
} }
@@ -2,7 +2,6 @@ package com.billpaymentservice.bbps.entites;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@@ -89,4 +88,7 @@ public class ViewAllComplaintTicketStatusDetails extends Auditable {
@Column(name = "biller_name") @Column(name = "biller_name")
private String billerName; private String billerName;
@ManyToOne
@JoinColumn(name = "complaint_response_id")
private ComplaintResponseEntity complaintResponse;
} }
@@ -0,0 +1,174 @@
package com.billpaymentservice.bbps.filter;
import java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.RedisConnectionFailureException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.billpaymentservice.bbps.ratelimit.LocalBucketManager;
import com.billpaymentservice.bbps.util.Helper;
import io.github.bucket4j.Bucket;
public class RateLimitingFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitingFilter.class);
private static final long RATE_PER_WINDOW = 1L;
private static final long BURST = 0L;
private static final long WINDOW_MILLIS = 60_000L;
private final StringRedisTemplate redisTemplate;
private final LocalBucketManager localBucketManager;
private final ObjectMapper mapper = new ObjectMapper();
private final DefaultRedisScript<Long> incrWithExpireScript;
public RateLimitingFilter(StringRedisTemplate redisTemplate, LocalBucketManager localBucketManager) {
this.redisTemplate = redisTemplate;
this.localBucketManager = localBucketManager;
String script =
"local v = redis.call('INCR', KEYS[1]);" +
"if v == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1]) end;" +
"return v";
this.incrWithExpireScript = new DefaultRedisScript<>(script, Long.class);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String mid = validateAndExtractMid(request, response);
if (mid == null) return;
LOGGER.info("RateLimiter activated → path={} mid={}", request.getRequestURI(), mid);
response.setHeader("X-RateLimit-Filter", "invoked");
long limit = RATE_PER_WINDOW + BURST;
String redisKey = buildRedisKey(mid);
// 1️⃣ Local Bucket Check
if (!consumeLocalToken(mid, response)) {
long resetAt = calculateResetTime();
setRateHeaders(response, limit, 0, resetAt);
writeErrorResponse(request, response, 429, "Too Many Requests", "Rate limit exceeded (local)", mid);
return;
}
// 2️⃣ Redis Counter
Long count = incrementRedisCounter(redisKey);
long resetAt = calculateResetTime();
long remaining = Math.max(0, limit - count);
setRateHeaders(response, limit, remaining, resetAt);
response.setHeader("X-RateLimit-Count", String.valueOf(count));
if (count > limit) {
handleGlobalLimitExceeded(mid, response, request, resetAt);
return;
}
filterChain.doFilter(request, response);
} // -------------------------HELPERS-------------------------
private String validateAndExtractMid(HttpServletRequest request, HttpServletResponse response) throws IOException
{ String mid = request.getHeader("mid");
if (mid == null || mid.trim().isEmpty())
{ writeErrorResponse(request, response, 400, "Bad Request", "Required header 'mid' is missing or empty", null);
return null; }
return mid.trim(); }
private boolean consumeLocalToken(String mid, HttpServletResponse response) {
Bucket bucket = localBucketManager.getBucket("local:mid:" + mid);
boolean consumed = bucket.tryConsume(1);
response.setHeader("X-RateLimit-Local-Remaining", String.valueOf(bucket.getAvailableTokens()));
return consumed;
}
private Long incrementRedisCounter(String key) {
long ttlSeconds = (WINDOW_MILLIS / 1000L) + 2;
try {
Long result = redisTemplate.execute(
incrWithExpireScript,
Collections.singletonList(key),
String.valueOf(ttlSeconds)
);
return (result == null ? 1 : result);
} catch (Exception e) {
LOGGER.warn("Redis unavailable → fallback mode");
return 1L;
}
}
private long calculateResetTime() {
long nowSec = System.currentTimeMillis() / 1000L;
long win = WINDOW_MILLIS / 1000L;
return ((nowSec / win) * win) + win;
}
private String buildRedisKey(String mid) {
long index = System.currentTimeMillis() / WINDOW_MILLIS;
return "ratelimit:mid:" + mid + ":ts:" + index;
}
private void setRateHeaders(HttpServletResponse response, long limit, long remaining, long resetAt) {
response.setHeader("X-RateLimit-Limit", String.valueOf(limit));
response.setHeader("X-RateLimit-Remaining", String.valueOf(remaining));
response.setHeader("X-RateLimit-Reset", String.valueOf(resetAt));
}
private void handleGlobalLimitExceeded(String mid,
HttpServletResponse res,
HttpServletRequest req,
long resetAt) throws IOException {
long now = System.currentTimeMillis() / 1000L;
res.setHeader("Retry-After", String.valueOf(Math.max(1, resetAt - now)));
writeErrorResponse(req, res, 429,
"Too Many Requests", "Rate limit exceeded (global)", mid);
}
private void writeErrorResponse(HttpServletRequest request,
HttpServletResponse response,
int status, String error,
String message, String mid) throws IOException {
if (response.isCommitted()) return;
Map<String, Object> body = new HashMap<>();
body.put("timestamp", Instant.now().toString());
body.put("status", status);
body.put("error", error);
body.put("message", message);
body.put("path", request.getRequestURI());
if (mid != null) body.put("mid", mid);
response.setStatus(status);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(response.getWriter(), body);
}
}
@@ -0,0 +1,34 @@
package com.billpaymentservice.bbps.ratelimit;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.stereotype.Component;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
@Component
public class LocalBucketManager {
// match the filter: 1 request per minute, no burst
private static final long RATE_PER_WINDOW = 1L;
private static final long BURST = 0L;
private static final Duration WINDOW_DURATION = Duration.ofMinutes(1);
private final ConcurrentMap<String, Bucket> cache = new ConcurrentHashMap<>();
public Bucket getBucket(String key) {
return cache.computeIfAbsent(key, k -> createBucket());
}
private Bucket createBucket() {
Bandwidth limit = Bandwidth.classic(RATE_PER_WINDOW + BURST, Refill.greedy(RATE_PER_WINDOW, WINDOW_DURATION));
return Bucket4j.builder()
.addLimit(limit)
.build();
}
}
@@ -4,17 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository;
import com.billpaymentservice.bbps.entites.BillMaster; import com.billpaymentservice.bbps.entites.BillMaster;
import java.util.Optional; public interface BillMasterRepository extends JpaRepository<BillMaster, String> {
/**
* Repository for BillMaster. Added convenience finder methods so service
* can look up records by client request id (reqId) or txnId.
*/
public interface BillMasterRepository extends JpaRepository<BillMaster, Long> {
Optional<BillMaster> findByReqId(String reqId);
Optional<BillMaster> findByTxnId(String txnId);
BillMaster findByInitiatorTxnId(String initiatorTxnId);
} }
@@ -6,8 +6,6 @@ import org.springframework.stereotype.Repository;
import com.billpaymentservice.bbps.entites.BillValidation; import com.billpaymentservice.bbps.entites.BillValidation;
@Repository @Repository
public interface BillValidationRepository extends JpaRepository<BillValidation, Long> { public interface BillValidationRepository extends JpaRepository<BillValidation, String> {
BillValidation findByInitiatorTxnId(String txnId);
} }
@@ -15,17 +15,7 @@ public interface ComplaintResponseRepository extends JpaRepository<ComplaintResp
// @Query("SELECT c FROM ComplaintResponseEntity c WHERE c.userId = :userId") // @Query("SELECT c FROM ComplaintResponseEntity c WHERE c.userId = :userId")
// List<ComplaintResponseEntity> findByUserIdCustom(@Param("userId") String userId); // List<ComplaintResponseEntity> findByUserIdCustom(@Param("userId") String userId);
// @Query("SELECT c FROM ComplaintResponseEntity c WHERE c.userId = :userId") @Query("SELECT c FROM ComplaintResponseEntity c WHERE c.userId = :userId")
// Page<ComplaintResponseEntity> findByUserIdCustom(@Param("userId") String userId, Pageable pageable);
@Query("""
SELECT DISTINCT c FROM ComplaintResponseEntity c
LEFT JOIN FETCH c.complaintStatusList cs
LEFT JOIN FETCH c.historyList ch
LEFT JOIN FETCH c.statusDetailsList sd
LEFT JOIN FETCH c.viewAllList va
WHERE c.userId = :userId
""")
Page<ComplaintResponseEntity> findByUserIdCustom(@Param("userId") String userId, Pageable pageable); Page<ComplaintResponseEntity> findByUserIdCustom(@Param("userId") String userId, Pageable pageable);
@Query(value = "SELECT * FROM bbps_complaint_responses WHERE user_id = :userId", nativeQuery = true) @Query(value = "SELECT * FROM bbps_complaint_responses WHERE user_id = :userId", nativeQuery = true)
@@ -1,16 +1,9 @@
package com.billpaymentservice.bbps.repository; package com.billpaymentservice.bbps.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import com.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog;
public interface OutboundTraceLogRepository extends JpaRepository<OutboundTraceLog, Long> { public interface OutboundTraceLogRepository extends JpaRepository<OutboundTraceLog, Long> {
Optional<OutboundTraceLog> findTopByTxnIdAndApiNameOrderByIdDesc(String txnId, String apiName);
List<OutboundTraceLog> findByTxnId(String txnId);
} }
@@ -1,6 +1,17 @@
package com.billpaymentservice.bbps.repository; package com.billpaymentservice.bbps.repository;
// Legacy stub: PaymentInitiate table removed; keep a placeholder class to avoid compilation errors if referenced. import java.util.Optional;
public class PaymentIntiateRepository {
// Intentionally empty - PaymentInitiate is no longer a JPA entity and this repo is retired. import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.billpaymentservice.bbps.entites.PaymentInitiate;
public interface PaymentIntiateRepository extends JpaRepository<PaymentInitiate, String> {
// Optional<PaymentInitiate> findByReqId(String clientReqId);
@Query(value = "SELECT * FROM bbps_payment_initiate WHERE req_id = :clientReqId", nativeQuery = true)
Optional<PaymentInitiate> findByReqId(String clientReqId);
} }
@@ -1,7 +1,6 @@
package com.billpaymentservice.bbps.repository; package com.billpaymentservice.bbps.repository;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
@@ -11,7 +10,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import com.billpaymentservice.bbps.entites.TransactionMaster; import com.billpaymentservice.bbps.entites.TransactionMaster;
public interface TransactionMasterRepository extends JpaRepository<TransactionMaster, Long> { public interface TransactionMasterRepository extends JpaRepository<TransactionMaster, String> {
// @Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid OR t.clientUserId = :clientUserId") // @Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid OR t.clientUserId = :clientUserId")
// List<TransactionMaster> findByMidOrClientUserId(@Param("mid") String mid, // List<TransactionMaster> findByMidOrClientUserId(@Param("mid") String mid,
@@ -21,29 +20,11 @@ public interface TransactionMasterRepository extends JpaRepository<TransactionMa
@Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid") @Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid")
Page<TransactionMaster> findByMid(@Param("mid") String mid, Pageable pageable); Page<TransactionMaster> findByMid(@Param("mid") String mid, Pageable pageable);
@Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid AND t.userId = :userId") @Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid AND t.clientUserId = :clientUserId")
Page<TransactionMaster> findByMidAndUserId( Page<TransactionMaster> findByMidOrClientUserId(@Param("mid") String mid,
@Param("mid") String mid, @Param("clientUserId") String clientUserId, Pageable pageable);
@Param("userId") String userId,
Pageable pageable
);
@Query("SELECT t FROM TransactionMaster t WHERE t.txnId = :txnId") @Query("SELECT t FROM TransactionMaster t WHERE t.txnId = :txnId")
List<TransactionMaster> findByTxnId(@Param("txnId") String txnId); List<TransactionMaster> findByTxnId(@Param("txnId") String txnId);
// Find TransactionMaster(s) by the payment_ref_id column (references PaymentStartRecord.txn_id)
List<TransactionMaster> findByPaymentRefId(String paymentRefId);
// New finders to replace PaymentInitiate usage
List<TransactionMaster> findByClientReqId(String clientReqId);
List<TransactionMaster> findByStatus(String status);
List<TransactionMaster> findByStatusIn(List<String> statuses);
// Find by initiatorTxnId so services can look up records created during initiatePayment
TransactionMaster findByInitiatorTxnId(String initiatorTxnId);
Optional<TransactionMaster> findByBankTxnId(String txnId);
} }
@@ -1,3 +1,4 @@
package com.billpaymentservice.bbps.serviceImpl; package com.billpaymentservice.bbps.serviceImpl;
import java.util.Map; import java.util.Map;
@@ -22,14 +23,12 @@ import com.billpaymentservice.bbps.dto.sentRequest.FetchBillForwardRequest;
import com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest; import com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest;
import com.billpaymentservice.bbps.entites.BillInitiate; import com.billpaymentservice.bbps.entites.BillInitiate;
import com.billpaymentservice.bbps.entites.BillMaster; import com.billpaymentservice.bbps.entites.BillMaster;
import com.billpaymentservice.bbps.entites.BillValidation;
import com.billpaymentservice.bbps.entites.InboundTraceLog; import com.billpaymentservice.bbps.entites.InboundTraceLog;
import com.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog;
import com.billpaymentservice.bbps.exception.CustomException; import com.billpaymentservice.bbps.exception.CustomException;
import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository; import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository;
import com.billpaymentservice.bbps.repository.BillInitateRepository; import com.billpaymentservice.bbps.repository.BillInitateRepository;
import com.billpaymentservice.bbps.repository.BillMasterRepository; import com.billpaymentservice.bbps.repository.BillMasterRepository;
import com.billpaymentservice.bbps.repository.BillValidationRepository;
import com.billpaymentservice.bbps.repository.InboundTraceLogRepository; import com.billpaymentservice.bbps.repository.InboundTraceLogRepository;
import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository; import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository;
import com.billpaymentservice.bbps.service.AgentService; import com.billpaymentservice.bbps.service.AgentService;
@@ -54,7 +53,6 @@ public class AgentServiceImpl implements AgentService {
private final BillerMapperHelper mapperHelper; private final BillerMapperHelper mapperHelper;
private final BillMasterRepository billMasterRepository; private final BillMasterRepository billMasterRepository;
private final BillInitateRepository billIntiateRepository; private final BillInitateRepository billIntiateRepository;
private final BillValidationRepository billValidationRepository;
private static final Logger LOGGER = LoggerFactory.getLogger(AgentServiceImpl.class); private static final Logger LOGGER = LoggerFactory.getLogger(AgentServiceImpl.class);
// Reusable ObjectMapper instance // Reusable ObjectMapper instance
@@ -65,7 +63,7 @@ public class AgentServiceImpl implements AgentService {
BharatConnectMasterRepository beneficiaryDetailsRepository, BharatConnectMasterRepository beneficiaryDetailsRepository,
InboundTraceLogRepository inboundTraceLogRepository, OutboundTraceLogRepository outboundTraceLogRepository, InboundTraceLogRepository inboundTraceLogRepository, OutboundTraceLogRepository outboundTraceLogRepository,
BillMasterRepository billMasterRepository, BillInitateRepository billIntiateRepository, BillMasterRepository billMasterRepository, BillInitateRepository billIntiateRepository,
BillerMapperHelper mapperHelper,BillValidationRepository billValidationRepository) { BillerMapperHelper mapperHelper) {
this.restClient = restClient; this.restClient = restClient;
this.httpService = httpService; this.httpService = httpService;
this.helper = helper; this.helper = helper;
@@ -73,7 +71,6 @@ public class AgentServiceImpl implements AgentService {
this.billMasterRepository = billMasterRepository; this.billMasterRepository = billMasterRepository;
this.billIntiateRepository = billIntiateRepository; this.billIntiateRepository = billIntiateRepository;
this.mapperHelper = mapperHelper; this.mapperHelper = mapperHelper;
this.billValidationRepository=billValidationRepository;
} }
@Override @Override
@@ -99,12 +96,6 @@ public class AgentServiceImpl implements AgentService {
BBPSConstant.HTTP_POST, forwardJson); BBPSConstant.HTTP_POST, forwardJson);
LOGGER.debug("Outbound log created successfully for txnId: {} and reqId: {}", txnId, requestId); LOGGER.debug("Outbound log created successfully for txnId: {} and reqId: {}", txnId, requestId);
BillMaster billMaster =new BillMaster();
billMaster.setReqId(fetchBillRequest.getReqId());
billMaster.setInitiatorTxnId(txnId);
billMaster.setMid(fetchBillRequest.getMid());
billMaster.setClientUserId(fetchBillRequest.getClientUserId());
billMasterRepository.save(billMaster);
try { try {
LOGGER.info("[{}] Calling external API | URL={}", apiName, fullUrl); LOGGER.info("[{}] Calling external API | URL={}", apiName, fullUrl);
Map<String, String> responseMap = httpService.postRequest(fullUrl, forwardJson, Map<String, String> responseMap = httpService.postRequest(fullUrl, forwardJson,
@@ -125,12 +116,25 @@ public class AgentServiceImpl implements AgentService {
String.valueOf(dataMap.get("remarks"))); String.valueOf(dataMap.get("remarks")));
} }
BillMaster billMaster = new BillMaster();
billMaster.setBillId((String) dataMap.get("billId"));
billMaster.setReqId(fetchBillRequest.getReqId());
billMaster.setTxnId(txnId);
billMaster.setClientUserId(fetchBillRequest.getClientUserId());
billMasterRepository.save(billMaster);
LOGGER.info("BillMaster saved for billId: {}", billMaster.getBillId()); LOGGER.info("BillMaster saved for billId: {}", billMaster.getBillId());
FetchBillResponse fetchBillResponse = objectMapper.convertValue(dataMap, FetchBillResponse.class); FetchBillResponse fetchBillResponse = objectMapper.convertValue(dataMap, FetchBillResponse.class);
GeneralResp generalResponse = fetchBillResponse.getGenericResponse(); GeneralResp generalResponse = fetchBillResponse.getGenericResponse();
helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus()); helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus());
BillInitiate billInitiate = new BillInitiate();
billInitiate.setBillId((String) dataMap.get("billId"));
billInitiate.setReqId(fetchBillRequest.getReqId());
billInitiate.setTxnId(txnId);
billInitiate.setClientUserId(fetchBillRequest.getClientUserId());
billIntiateRepository.save(billInitiate);
LOGGER.info("BillInitiate entry saved for billId: {}", billInitiate.getBillId());
BillFetchByBillIdRequest billFetchByBillIdRequest = new BillFetchByBillIdRequest(); BillFetchByBillIdRequest billFetchByBillIdRequest = new BillFetchByBillIdRequest();
billFetchByBillIdRequest.setBillId(fetchBillResponse.getBillId()); billFetchByBillIdRequest.setBillId(fetchBillResponse.getBillId());
@@ -143,15 +147,13 @@ public class AgentServiceImpl implements AgentService {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
LOGGER.warn("Thread interrupted during delay: {}", e.getMessage()); LOGGER.warn("Thread interrupted during delay: {}", e.getMessage());
} }
LOGGER.info("Calling internal method Fetching bill by ID: {}", billFetchByBillIdRequest.getBillId()); LOGGER.info("Calling internal method Fetching bill by ID: {}", billFetchByBillIdRequest.getBillId());
LOGGER.info("[{}] END | reqId={} | status={}", apiName, requestId, generalResponse.getStatus()); LOGGER.info("[{}] END | reqId={} | status={}", apiName, requestId, generalResponse.getStatus());
return fetchBillById(billFetchByBillIdRequest, inboundLog); return fetchBillById(billFetchByBillIdRequest, inboundLog);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("JSON Parsing Error: {}", e.getMessage(), e); LOGGER.error("JSON Parsing Error: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
// Use standardized user-facing message and status, keep internal details in remarks throw new CustomException("Invalid JSON received from API");
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -169,8 +171,6 @@ public class AgentServiceImpl implements AgentService {
LOGGER.debug("Outbound log created successfully for FetchBillById: {}", fullUrl); LOGGER.debug("Outbound log created successfully for FetchBillById: {}", fullUrl);
try { try {
LOGGER.info("Calling external API: {}", fullUrl); LOGGER.info("Calling external API: {}", fullUrl);
Map<String, String> responseMap = httpService.getRequest(fullUrl, Map<String, String> responseMap = httpService.getRequest(fullUrl,
helper.buildHeaders(beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID))); helper.buildHeaders(beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID)));
@@ -194,22 +194,19 @@ public class AgentServiceImpl implements AgentService {
BillDataResponse responseWrapper = objectMapper.convertValue(dataMap, BillDataResponse.class); BillDataResponse responseWrapper = objectMapper.convertValue(dataMap, BillDataResponse.class);
GeneralResp generalResp = responseWrapper.getGenericResponse(); GeneralResp generalResp = responseWrapper.getGenericResponse();
BillMaster
existingbill=billMasterRepository.findByInitiatorTxnId(
inboundLog.getTxnId());
String status = generalResp.getStatus(); String status = generalResp.getStatus();
LOGGER.info("Current Bill Status: {}", status); LOGGER.info("Current Bill Status: {}", status);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
mapperHelper.saveOrUpdateBillData(responseWrapper, billFetchByBillIdRequest, txnId, reqId,billFetchByBillIdRequest.getClientUserId(), existingbill); mapperHelper.saveOrUpdateBillData(responseWrapper, billFetchByBillIdRequest, txnId, reqId, userId);
LOGGER.info("Bill data saved successfully for billId: {}", billId); LOGGER.info("Bill data saved successfully for billId: {}", billId);
return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper); Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing BillById JSON: {}", e.getMessage(), e); LOGGER.error("Error parsing BillById JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -258,7 +255,7 @@ public class AgentServiceImpl implements AgentService {
// update inbound log success and persist bill data // update inbound log success and persist bill data
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
//mapperHelper.saveOrUpdateBillData(responseWrapper, billFetchByBillIdRequest, txnId, reqId, userId); mapperHelper.saveOrUpdateBillData(responseWrapper, billFetchByBillIdRequest, txnId, reqId, userId);
LOGGER.info("Bill data saved successfully for billId={} | Status={}", billId, generalResp.getStatus()); LOGGER.info("Bill data saved successfully for billId={} | Status={}", billId, generalResp.getStatus());
LOGGER.info("[{}] END | reqId={} | status={}", apiName, reqId, generalResp.getStatus()); LOGGER.info("[{}] END | reqId={} | status={}", apiName, reqId, generalResp.getStatus());
return responseModel; return responseModel;
@@ -268,7 +265,7 @@ public class AgentServiceImpl implements AgentService {
catch (JsonProcessingException e) { catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON for billId={} : {}", billId, e.getMessage(), e); LOGGER.error("Error parsing responseData JSON for billId={} : {}", billId, e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -297,14 +294,8 @@ public class AgentServiceImpl implements AgentService {
forwardJson); forwardJson);
LOGGER.debug("Outbound log created successfully for txnId: {} and reqId: {}", txnId, reqId); LOGGER.debug("Outbound log created successfully for txnId: {} and reqId: {}", txnId, reqId);
BillValidation billValidation = new BillValidation();
billValidation.setReqId(fetchBillValidateRequest.getReqId());
billValidation.setInitiatorTxnId(txnId);
billValidation.setMid(fetchBillValidateRequest.getMid());
billValidation.setClientUserId(fetchBillValidateRequest.getClientUserId());
billValidationRepository.save(billValidation);
try { try {
LOGGER.info("[{}] Calling external API | URL={}", apiName, fullUrl); LOGGER.info("[{}] Calling external API | URL={}", apiName, fullUrl);
Map<String, String> responseMap = httpService.postRequest(fullUrl, forwardJson, Map<String, String> responseMap = httpService.postRequest(fullUrl, forwardJson,
helper.buildHeaders(beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID))); helper.buildHeaders(beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID)));
@@ -324,10 +315,23 @@ public class AgentServiceImpl implements AgentService {
String.valueOf(dataMap.get("remarks"))); String.valueOf(dataMap.get("remarks")));
} }
BillMaster billMaster = new BillMaster();
billMaster.setBillId((String) dataMap.get("billId"));
billMaster.setReqId(fetchBillValidateRequest.getReqId());
billMaster.setTxnId(txnId);
billMasterRepository.save(billMaster);
LOGGER.info("BillMaster saved for billId: {}", billMaster.getBillId());
FetchBillResponse fetchBillResponse = objectMapper.convertValue(dataMap, FetchBillResponse.class); FetchBillResponse fetchBillResponse = objectMapper.convertValue(dataMap, FetchBillResponse.class);
GeneralResp generalResponse = fetchBillResponse.getGenericResponse(); GeneralResp generalResponse = fetchBillResponse.getGenericResponse();
helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus()); helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus());
BillInitiate billInitiate = new BillInitiate();
billInitiate.setBillId((String) dataMap.get("billId"));
billInitiate.setReqId(fetchBillValidateRequest.getReqId());
billInitiate.setTxnId(txnId);
billIntiateRepository.save(billInitiate);
LOGGER.info("BillInitiate entry saved for billId: {}", billInitiate.getBillId());
BillFetchByBillIdRequest billFetchByBillIdRequest = new BillFetchByBillIdRequest(); BillFetchByBillIdRequest billFetchByBillIdRequest = new BillFetchByBillIdRequest();
billFetchByBillIdRequest.setBillId(fetchBillResponse.getBillId()); billFetchByBillIdRequest.setBillId(fetchBillResponse.getBillId());
billFetchByBillIdRequest.setMid(fetchBillValidateRequest.getMid()); billFetchByBillIdRequest.setMid(fetchBillValidateRequest.getMid());
@@ -345,7 +349,7 @@ public class AgentServiceImpl implements AgentService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("JSON Parsing Error: {}", e.getMessage(), e); LOGGER.error("JSON Parsing Error: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS ,BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -359,10 +363,6 @@ public class AgentServiceImpl implements AgentService {
String billId = billRequest.getBillId(); String billId = billRequest.getBillId();
String fullUrl = agentDtaUrl + BBPSConstant.BILL_VALIDATE_ID_URL + billRequest.getBillId(); String fullUrl = agentDtaUrl + BBPSConstant.BILL_VALIDATE_ID_URL + billRequest.getBillId();
BillValidation
billValidation=billValidationRepository.findByInitiatorTxnId(
inboundLog.getTxnId());
billValidation.setCheckerTxnId(txnId);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, reqId, apiName, fullUrl, OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, reqId, apiName, fullUrl,
BBPSConstant.HTTP_GET, null); BBPSConstant.HTTP_GET, null);
LOGGER.debug("Outbound log created successfully for FetchBillById: {}", fullUrl); LOGGER.debug("Outbound log created successfully for FetchBillById: {}", fullUrl);
@@ -393,19 +393,18 @@ public class AgentServiceImpl implements AgentService {
FetchBillValidateResponse.class); FetchBillValidateResponse.class);
GeneralResp generalResp = responseWrapper.getGenericResponse(); GeneralResp generalResp = responseWrapper.getGenericResponse();
String status = generalResp.getStatus(); String status = generalResp.getStatus();
LOGGER.info("Current Bill Status: {}", status); LOGGER.info("Current Bill Status: {}", status);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
System.out.println("responseWrapper:" + responseWrapper); System.out.println("responseWrapper:" + responseWrapper);
mapperHelper.saveBillValidation(responseWrapper, billRequest, txnId,billValidation); mapperHelper.saveBillValidation(responseWrapper, billRequest, txnId);
return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper); Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing BillById JSON: {}", e.getMessage(), e); LOGGER.error("Error parsing BillById JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -457,7 +456,7 @@ public class AgentServiceImpl implements AgentService {
GeneralResp generalResp = responseWrapper.getGenericResponse(); GeneralResp generalResp = responseWrapper.getGenericResponse();
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
//mapperHelper.saveBillValidation(responseWrapper, billRequest, txnId); mapperHelper.saveBillValidation(responseWrapper, billRequest, txnId);
LOGGER.info("Bill validation saved successfully for billId: {}", billId); LOGGER.info("Bill validation saved successfully for billId: {}", billId);
LOGGER.info("[{}] END | reqId={} | status={}", apiName, reqId, generalResp.getStatus()); LOGGER.info("[{}] END | reqId={} | status={}", apiName, reqId, generalResp.getStatus());
return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
@@ -466,10 +465,17 @@ public class AgentServiceImpl implements AgentService {
} catch (com.fasterxml.jackson.core.JsonProcessingException e) { } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
LOGGER.error("Error parsing ValidateByBillId JSON: {}", e.getMessage(), e); LOGGER.error("Error parsing ValidateByBillId JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
// ================================== Bill fetch and Bill Validate by Client Req
// Id==================================//
// =======
// ... remaining methods unchanged except where headers/objectMapper were
// previously created (they now use buildHeaders() and the class-level
// objectMapper)
// >>>>>>>d0812656ff2aee80b1eb0c0ab8e29a01d7d66553
@Override @Override
public ResponseModel fetchBillValidateByClientReqId(BillValidateByClientIdRequest billValidateByClientIdRequest) { public ResponseModel fetchBillValidateByClientReqId(BillValidateByClientIdRequest billValidateByClientIdRequest) {
@@ -488,12 +494,7 @@ public class AgentServiceImpl implements AgentService {
helper.validateMerchant(billValidateByClientIdRequest.getMid()); helper.validateMerchant(billValidateByClientIdRequest.getMid());
Optional<BillInitiate> billInitiate = billIntiateRepository.findByReqId(clientReqId); Optional<BillInitiate> billInitiate = billIntiateRepository.findByReqId(clientReqId);
if (billInitiate.isEmpty()) { if (billInitiate.isEmpty()) {
new CustomException( throw new CustomException(BBPSConstant.NO_BILL_FOUND);
BBPSConstant.NO_BILL_FOUND, // message // status
BBPSConstant.FAILED_STATUS,
BBPSConstant.HTTP_NOT_FOUND, // statusCode
"Bill not found in database for given clientReqId"
);
} }
String fullUrl = agentDtaUrl + BBPSConstant.BILL_VALIDATE_BY_CLIENT_REQ_ID + billInitiate.get().getTxnId(); String fullUrl = agentDtaUrl + BBPSConstant.BILL_VALIDATE_BY_CLIENT_REQ_ID + billInitiate.get().getTxnId();
@@ -530,7 +531,7 @@ public class AgentServiceImpl implements AgentService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -551,12 +552,7 @@ public class AgentServiceImpl implements AgentService {
Optional<BillInitiate> billInitiate = billIntiateRepository.findByReqId(clientReqId); Optional<BillInitiate> billInitiate = billIntiateRepository.findByReqId(clientReqId);
if (billInitiate.isEmpty()) { if (billInitiate.isEmpty()) {
throw new CustomException( throw new CustomException(BBPSConstant.NO_BILL_FOUND);
BBPSConstant.NO_BILL_FOUND, // message // status
BBPSConstant.FAILED_STATUS,
BBPSConstant.HTTP_NOT_FOUND, // statusCode
"Bill not found in database for given clientReqId"
);
} }
String fullUrl = agentDtaUrl + BBPSConstant.BILL_VALIDATE_BY_CLIENT_REQ_ID + billInitiate.get().getTxnId(); String fullUrl = agentDtaUrl + BBPSConstant.BILL_VALIDATE_BY_CLIENT_REQ_ID + billInitiate.get().getTxnId();
@@ -592,7 +588,7 @@ public class AgentServiceImpl implements AgentService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -76,8 +76,18 @@ public class AuthServiceImpl implements AuthService {
try { try {
Optional<UserEntity> byMobileNumber = userRepository.findByMobile(mobileNo); Optional<UserEntity> byMobileNumber = userRepository.findByMobile(mobileNo);
if (byMobileNumber.isPresent()) { if (byMobileNumber.isPresent()) {
UserEntity user = byMobileNumber.get(); UserEntity user = byMobileNumber.get();
LOGGER.info("User found for mobileNo: {}", user);
if(user.getIsDeleted() != true && user.isEnabled()== false) {
LOGGER.warn("User with mobileNo: {} is deleted", mobileNo);
return ResponseModel.builder().message("user account is disabled.please contact your service provider").status("Failure")
.statusCode(BBPSConstant.USER_IS_DELTED_IN_DB).data(null).build();
}
String token = jwtUtil.generateToken(user.getMobile()); String token = jwtUtil.generateToken(user.getMobile());
LOGGER.info("Generated JWT token for mobileNo: " + mobileNo); LOGGER.info("Generated JWT token for mobileNo: " + mobileNo);
@@ -15,7 +15,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient;
import com.billpaymentservice.bbps.dto.ComplaintResponseDto;
import com.billpaymentservice.bbps.dto.GeneralResp; import com.billpaymentservice.bbps.dto.GeneralResp;
import com.billpaymentservice.bbps.dto.ResponseModel; import com.billpaymentservice.bbps.dto.ResponseModel;
import com.billpaymentservice.bbps.dto.request.AllMasterOptionsRequest; import com.billpaymentservice.bbps.dto.request.AllMasterOptionsRequest;
@@ -104,7 +103,6 @@ public class ComplainServiceImpl implements ComplainService {
String requestId = complainRequestDTO.getReqId(); String requestId = complainRequestDTO.getReqId();
String mid = complainRequestDTO.getMid(); String mid = complainRequestDTO.getMid();
String userId = complainRequestDTO.getClientUserId(); String userId = complainRequestDTO.getClientUserId();
String requestTxnId = complainRequestDTO.getTxnId();
LOGGER.info("[{}] START | reqId={} | mid={} | clientUserId={}", apiName, requestId, mid, userId); LOGGER.info("[{}] START | reqId={} | mid={} | clientUserId={}", apiName, requestId, mid, userId);
String txnId = helper.generateTxnId(15); String txnId = helper.generateTxnId(15);
@@ -127,7 +125,7 @@ public class ComplainServiceImpl implements ComplainService {
try { try {
Optional<TransactionMaster> dataById = transactionMasterRepository.findByBankTxnId(complainRequestDTO.getTxnId()); Optional<TransactionMaster> dataById = transactionMasterRepository.findById(complainRequestDTO.getTxnId());
if (dataById == null || dataById.isEmpty()) { if (dataById == null || dataById.isEmpty()) {
LOGGER.warn("Txn Data Not Found This Txn Id is null"); LOGGER.warn("Txn Data Not Found This Txn Id is null");
throw new CustomException(BBPSConstant.USER_Txn_IS_NOT_FOUND, "FAILED", BBPSConstant.BADREQUEST, null); throw new CustomException(BBPSConstant.USER_Txn_IS_NOT_FOUND, "FAILED", BBPSConstant.BADREQUEST, null);
@@ -135,7 +133,7 @@ public class ComplainServiceImpl implements ComplainService {
TransactionMaster transData = dataById.get(); TransactionMaster transData = dataById.get();
// Inject tenantId + agentId into request JSON // Inject tenantId + agentId into request JSON
String transactionCreatedDateString = transData.getCreationDateBank(); String transactionCreatedDateString = transData.getCreationDate();
LOGGER.info("Date is coming -:" + transactionCreatedDateString); LOGGER.info("Date is coming -:" + transactionCreatedDateString);
LOGGER.info("Return Date Time After 24 Hours" LOGGER.info("Return Date Time After 24 Hours"
+ BillerMapperHelper.isComplaintAllowed(transactionCreatedDateString)); + BillerMapperHelper.isComplaintAllowed(transactionCreatedDateString));
@@ -197,7 +195,7 @@ public class ComplainServiceImpl implements ComplainService {
ComplaintResponse responseWrapper = objectMapper.convertValue(dataMap, ComplaintResponse.class); ComplaintResponse responseWrapper = objectMapper.convertValue(dataMap, ComplaintResponse.class);
String saveComplaintResponseEntity = mapperHelper.saveComplaintResponseEntity(responseWrapper, requestId, String saveComplaintResponseEntity = mapperHelper.saveComplaintResponseEntity(responseWrapper, requestId,
txnId, userId, requestTxnId, mid); txnId, userId);
LOGGER.info("Save Complaint Response Count" + saveComplaintResponseEntity); LOGGER.info("Save Complaint Response Count" + saveComplaintResponseEntity);
ResponseModel responseModel = new ResponseModel(responseWrapper.getMessage(), responseWrapper.getStatus(), ResponseModel responseModel = new ResponseModel(responseWrapper.getMessage(), responseWrapper.getStatus(),
Integer.parseInt(responseWrapper.getStatusCode()), responseWrapper); Integer.parseInt(responseWrapper.getStatusCode()), responseWrapper);
@@ -209,8 +207,7 @@ public class ComplainServiceImpl implements ComplainService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("JSON Parsing Error: {}", e.getMessage()); LOGGER.error("JSON Parsing Error: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", throw new CustomException("Invalid JSON received from API");
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -293,8 +290,7 @@ public class ComplainServiceImpl implements ComplainService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing JSON in complaint status API: {}", e.getMessage(), e); LOGGER.error("Error parsing JSON in complaint status API: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", throw new CustomException("Invalid JSON received from API");
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -360,8 +356,7 @@ public class ComplainServiceImpl implements ComplainService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e); LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", throw new CustomException("Invalid JSON received from API");
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -425,8 +420,7 @@ public class ComplainServiceImpl implements ComplainService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e); LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", throw new CustomException("Invalid JSON received from API");
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -538,8 +532,7 @@ public class ComplainServiceImpl implements ComplainService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e); LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", throw new CustomException("Invalid JSON received from API");
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -606,8 +599,7 @@ public class ComplainServiceImpl implements ComplainService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e); LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", throw new CustomException("Invalid JSON received from API");
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -663,15 +655,14 @@ public class ComplainServiceImpl implements ComplainService {
LOGGER.info("[{}] Complaints found for userId={} | count={}", apiName, userId, LOGGER.info("[{}] Complaints found for userId={} | count={}", apiName, userId,
complaintUserData.getContent().size()); complaintUserData.getContent().size());
List<ComplaintResponseDto> dtoList = complaintUserData.getContent().stream().map(mapperHelper::toDto)
.toList();
// Update outbound log with the count // Update outbound log with the count
String count = String.valueOf(dtoList.size()); String count = String.valueOf(complaintUserData.getContent().size());
helper.updateOutboundLogResponse(outboundLog, count); helper.updateOutboundLogResponse(outboundLog, count);
// Return successful response with complaints data // Return successful response with complaints data
return new ResponseModel(BBPSConstant.ALL_COMPLAINT_FIELDS_FETCH, BBPSConstant.SUCCESS_RESPONSE, return new ResponseModel(BBPSConstant.ALL_COMPLAINT_FIELDS_FETCH, BBPSConstant.SUCCESS_RESPONSE,
BBPSConstant.HTTP_OK_INT, dtoList, dtoList.size()); BBPSConstant.HTTP_OK_INT, complaintUserData.getContent(), // Only the list of complaints (data)
complaintUserData.getContent().size());
} catch (CustomException e) { } catch (CustomException e) {
// Rethrow custom exception for handling at the controller level // Rethrow custom exception for handling at the controller level
@@ -181,15 +181,20 @@ public class HttpService {
try { try {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
log.info("🌐 Sending FORM POST to: {}", url); log.info("🌐 Sending FORM POST to: {}", url);
log.info("📦 Payload: {}", formBody); log.info("📦 Payload: {}", formBody);
ResponseEntity<String> restResponse = restClient.post() ResponseEntity<String> restResponse = restClient.post()
.uri(url) .uri(url)
.headers(h -> h.addAll(headers)) .headers(h -> h.addAll(headers))
.body(formBody) .body(formBody)
.retrieve() .retrieve()
.toEntity(String.class); .toEntity(String.class);
response.put("respCode", String.valueOf(restResponse.getStatusCode().value())); response.put("respCode", String.valueOf(restResponse.getStatusCode().value()));
// HTML RESPONSE directly stored
response.put("data", restResponse.getBody()); response.put("data", restResponse.getBody());
log.info("✅ FORM POST Response Status: {}", restResponse.getStatusCode()); log.info("✅ FORM POST Response Status: {}", restResponse.getStatusCode());
@@ -164,8 +164,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
// Use user-friendly message constant and keep internal details in remarks throw new CustomException("Invalid JSON received from API");
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
@@ -228,8 +227,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
// Unified constant for invalid JSON throw new CustomException("Invalid JSON received from API");
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -300,7 +298,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -366,7 +364,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -433,7 +431,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -509,7 +507,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -572,7 +570,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -638,7 +636,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -702,7 +700,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -759,7 +757,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS , BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -831,7 +829,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -889,7 +887,7 @@ public class MasterDataServiceImpl implements MasterDataService {
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing responseData JSON: {} :: class :: method ", e.getMessage()); LOGGER.error("Error parsing responseData JSON: {} :: class :: method ", e.getMessage());
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -1,6 +1,5 @@
package com.billpaymentservice.bbps.serviceImpl; package com.billpaymentservice.bbps.serviceImpl;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -13,7 +12,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.billpaymentservice.bbps.dto.GeneralResp; import com.billpaymentservice.bbps.dto.GeneralResp;
import com.billpaymentservice.bbps.dto.ResponseModel; import com.billpaymentservice.bbps.dto.ResponseModel;
import com.billpaymentservice.bbps.dto.TransactionDTO;
import com.billpaymentservice.bbps.dto.request.BillPaymentRequest; import com.billpaymentservice.bbps.dto.request.BillPaymentRequest;
import com.billpaymentservice.bbps.dto.request.TransactionStatusRequest; import com.billpaymentservice.bbps.dto.request.TransactionStatusRequest;
import com.billpaymentservice.bbps.dto.request.TrasactionStatusByClientReqIdRequest; import com.billpaymentservice.bbps.dto.request.TrasactionStatusByClientReqIdRequest;
@@ -25,12 +23,14 @@ import com.billpaymentservice.bbps.entites.BharatConnectMaster;
import com.billpaymentservice.bbps.entites.BillPayment; import com.billpaymentservice.bbps.entites.BillPayment;
import com.billpaymentservice.bbps.entites.InboundTraceLog; import com.billpaymentservice.bbps.entites.InboundTraceLog;
import com.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog;
import com.billpaymentservice.bbps.entites.PaymentInitiate;
import com.billpaymentservice.bbps.entites.TransactionMaster; import com.billpaymentservice.bbps.entites.TransactionMaster;
import com.billpaymentservice.bbps.exception.CustomException; import com.billpaymentservice.bbps.exception.CustomException;
import com.billpaymentservice.bbps.repository.AgentFundTransferRepository; import com.billpaymentservice.bbps.repository.AgentFundTransferRepository;
import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository; import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository;
import com.billpaymentservice.bbps.repository.InboundTraceLogRepository; import com.billpaymentservice.bbps.repository.InboundTraceLogRepository;
import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository; import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository;
import com.billpaymentservice.bbps.repository.PaymentIntiateRepository;
import com.billpaymentservice.bbps.repository.PaymentRepository; import com.billpaymentservice.bbps.repository.PaymentRepository;
import com.billpaymentservice.bbps.repository.TransactionMasterRepository; import com.billpaymentservice.bbps.repository.TransactionMasterRepository;
import com.billpaymentservice.bbps.service.PaymentService; import com.billpaymentservice.bbps.service.PaymentService;
@@ -54,6 +54,7 @@ public class PaymentServiceImpl implements PaymentService {
private final PaymentRepository paymentRepository; private final PaymentRepository paymentRepository;
private final InboundTraceLogRepository inboundTraceLogRepository; private final InboundTraceLogRepository inboundTraceLogRepository;
private final OutboundTraceLogRepository outboundTraceLogRepository; private final OutboundTraceLogRepository outboundTraceLogRepository;
private final PaymentIntiateRepository paymentIntiateRepository;
private final TransactionMasterRepository transactionMasterRepository; private final TransactionMasterRepository transactionMasterRepository;
private final AgentFundTransferRepository agentFundTransferRepository; private final AgentFundTransferRepository agentFundTransferRepository;
private final BillerMapperHelper mapperHelper; private final BillerMapperHelper mapperHelper;
@@ -65,7 +66,8 @@ public class PaymentServiceImpl implements PaymentService {
public PaymentServiceImpl(BharatConnectMasterRepository beneficiaryDetailsRepository, HttpService httpService, public PaymentServiceImpl(BharatConnectMasterRepository beneficiaryDetailsRepository, HttpService httpService,
Helper helper, PaymentRepository paymentRepository, InboundTraceLogRepository inboundTraceLogRepository, Helper helper, PaymentRepository paymentRepository, InboundTraceLogRepository inboundTraceLogRepository,
OutboundTraceLogRepository outboundTraceLogRepository, TransactionMasterRepository transactionMasterRepository, OutboundTraceLogRepository outboundTraceLogRepository, PaymentIntiateRepository paymentIntiateRepository,
TransactionMasterRepository transactionMasterRepository,
AgentFundTransferRepository agentFundTransferRepository, BillerMapperHelper mapperHelper) { AgentFundTransferRepository agentFundTransferRepository, BillerMapperHelper mapperHelper) {
this.beneficiaryDetailsRepository = beneficiaryDetailsRepository; this.beneficiaryDetailsRepository = beneficiaryDetailsRepository;
this.httpService = httpService; this.httpService = httpService;
@@ -73,6 +75,7 @@ public class PaymentServiceImpl implements PaymentService {
this.paymentRepository = paymentRepository; this.paymentRepository = paymentRepository;
this.inboundTraceLogRepository = inboundTraceLogRepository; this.inboundTraceLogRepository = inboundTraceLogRepository;
this.outboundTraceLogRepository = outboundTraceLogRepository; this.outboundTraceLogRepository = outboundTraceLogRepository;
this.paymentIntiateRepository = paymentIntiateRepository;
this.transactionMasterRepository = transactionMasterRepository; this.transactionMasterRepository = transactionMasterRepository;
this.agentFundTransferRepository = agentFundTransferRepository; this.agentFundTransferRepository = agentFundTransferRepository;
this.mapperHelper = mapperHelper; this.mapperHelper = mapperHelper;
@@ -105,74 +108,92 @@ public class PaymentServiceImpl implements PaymentService {
String requestJson = helper.convertObjectToJson(billPaymentRequest); String requestJson = helper.convertObjectToJson(billPaymentRequest);
InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, billPaymentRequest.getMid(), InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, billPaymentRequest.getMid(),
BBPSConstant.HTTP_POST, requestJson); BBPSConstant.HTTP_POST, requestJson);
LOGGER.debug("Inbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId); LOGGER.debug("Inbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_POST, OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_POST,
forwardjson); forwardjson);
LOGGER.debug("Outbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId); LOGGER.debug("Outbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId);
TransactionMaster preTransaction=null; try {
preTransaction = new TransactionMaster(); LOGGER.info("Calling BBPS payment URL: {}", url);
preTransaction.setTxnId(txnId); Map<String, String> responseMap = httpService.postRequest(url, forwardjson, headers);
preTransaction.setInitiatorTxnId(txnId); String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
preTransaction.setReqId(requestId); LOGGER.info("Payment API Response: {}", responseData);
preTransaction.setMid(billPaymentRequest.getMid());
preTransaction.setUserId(billPaymentRequest.getClientUserId());
preTransaction.setClientReqId(billPaymentRequest.getClientReqId());
preTransaction.setStatus("PENDING");
EntityRelationshipUtil.linkTransactionMasterRelationships(preTransaction);
// ensure txnId exists (no uniqueness suffixing)
preTransaction.setTxnId(ensureTxnId(preTransaction.getTxnId()));
transactionMasterRepository.save(preTransaction);
LOGGER.info("Pre-request TransactionMaster saved with status=INITIATED for txnId={}", txnId);
LOGGER.info("Calling BBPS payment URL: {}", url); helper.updateOutboundLogResponse(outboundLog, responseData);
Map<String, String> responseMap = httpService.postRequest(url, forwardjson, headers); LOGGER.debug("Outbound log response updated for txnId={}", txnId);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); if (responseData == null || responseData.isEmpty()) {
LOGGER.info("Payment API Response: {}", responseData); helper.updateInboundLogFailure(inboundLog, BBPSConstant.EMPTY_RESPONSE_FROM_BBPS,
BBPSConstant.FAILED_STR);
LOGGER.warn("Empty response received from BBPS for txnId={}", txnId);
throw new CustomException(BBPSConstant.EMPTY_RESPONSE_FROM_BBPS);
}
Map<String, Object> dataMap = objectMapper.readValue(responseData,
new TypeReference<Map<String, Object>>() {
});
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERIC_KEY)) {
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_STRUCTURE);
LOGGER.warn("Invalid response structure received for txnId={}", txnId);
throw new CustomException(BBPSConstant.INVALID_STRUCTURE);
}
BillPaymentResponse billPaymentResponse = objectMapper.convertValue(dataMap, BillPaymentResponse.class);
GeneralResp generalResponse = billPaymentResponse.getGenericResponse();
LOGGER.info("genric Response" + generalResponse);
if(generalResponse.getStatus().equalsIgnoreCase("Failed")) {
throw new CustomException(generalResponse.getMessage(), generalResponse.getStatus(),
Integer.parseInt(generalResponse.getStatusCode()), generalResponse.getRemarks());
}
helper.updateOutboundLogResponse(outboundLog, responseData); BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class);
LOGGER.debug("Outbound log response updated for txnId={}", txnId); paymentRepository.save(entity);
LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus());
PaymentInitiate paymentInitiate = new PaymentInitiate();
paymentInitiate.setTxnId(txnId);
paymentInitiate.setUserId(userId);
paymentInitiate.setReqId(requestId);
paymentInitiate.setMid(billPaymentRequest.getMid());
paymentInitiate.setClientUserId(billPaymentRequest.getClientUserId());
paymentInitiate.setBankTxnId(billPaymentResponse.getTxnId());
paymentInitiate.setStatus(billPaymentResponse.getStatus());
paymentIntiateRepository.save(paymentInitiate);
LOGGER.info("PaymentInitiate entry saved successfully for txnId={}", txnId);
Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog); TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest();
transactionStatusRequest.setTxnId(billPaymentResponse.getTxnId());
BillPaymentResponse billPaymentResponse = objectMapper.convertValue(dataMap, BillPaymentResponse.class); transactionStatusRequest.setReqId(requestId);
transactionStatusRequest.setMid(billPaymentRequest.getMid());
GeneralResp generalResponse = billPaymentResponse.getGenericResponse(); transactionStatusRequest.setClientUserId(billPaymentRequest.getClientUserId());
LOGGER.info("genric Response" + generalResponse);
BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class);
paymentRepository.save(entity);
LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus());
preTransaction.setBankTxnId(entity.getTxnId());
transactionMasterRepository.save(preTransaction);
LOGGER.info("updated TransactionMaster with bankTxnId for txnId={}", txnId);
TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest();
transactionStatusRequest.setReqId(billPaymentRequest.getReqId());
transactionStatusRequest.setTxnId(entity.getTxnId());
ResponseModel responseModel = checkTrasactionStatus(transactionStatusRequest, inboundLog);
return responseModel;
try {
Thread.sleep((long) BBPSConstant.PAYMENT_STATUS_DELAY_MS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.warn("Thread interrupted during payment delay: {}", e.getMessage());
}
LOGGER.info("Checking transaction status for txnId={}", billPaymentResponse.getTxnId());
LOGGER.info("[{}] END | reqId={} | txnId={}", apiName, requestId, txnId);
return checkTrasactionStatus(transactionStatusRequest, inboundLog);
} catch (JsonProcessingException e) {
LOGGER.error("JSON Parsing Error in initiatePayment: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException("Invalid JSON received from API");
}
} }
public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest, public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest,
InboundTraceLog inboundLog) { InboundTraceLog inboundLog) {
String apiName = BBPSConstant.API_CHECK_TRANSACTION_STATUS; String apiName = BBPSConstant.API_CHECK_TRANSACTION_STATUS;
String checkerTxnId = helper.generateTxnId(15); String txnId = helper.generateTxnId(15);
String requestId = transactionStatusRequest.getReqId(); String requestId = transactionStatusRequest.getReqId();
// START log for internal transaction status check // START log for internal transaction status check
LOGGER.info("[{}] START (internal) | checkTxnId={} | reqId={} | bankTxnId={}", apiName, checkerTxnId, requestId, LOGGER.info("[{}] START (internal) | checkTxnId={} | reqId={} | bankTxnId={}", apiName, txnId, requestId,
transactionStatusRequest.getTxnId()); transactionStatusRequest.getTxnId());
String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId();
@@ -183,142 +204,132 @@ public class PaymentServiceImpl implements PaymentService {
BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID);
HttpHeaders headers = helper.buildHeaders(headersData); HttpHeaders headers = helper.buildHeaders(headersData);
OutboundTraceLog outboundLog = helper.createOutboundLog(checkerTxnId, requestId, apiName, url, BBPSConstant.HTTP_GET, OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_GET,
null); null);
LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", checkerTxnId, requestId); LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", txnId, requestId);
Map<String, String> responseMap = httpService.getRequest(url, headers); Map<String, String> responseMap = httpService.getRequest(url, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Transaction status response: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData); helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for internal status check | txnId={}", checkerTxnId); LOGGER.debug("Outbound log response updated for internal status check | txnId={}", txnId);
Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog); try {
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { Map<String, Object> dataMap = objectMapper.readValue(responseData,
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE); new TypeReference<Map<String, Object>>() {
LOGGER.warn("Invalid transaction status response for txnId={}", transactionStatusRequest.getTxnId()); });
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")), if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE);
String.valueOf(dataMap.get("remarks"))); LOGGER.warn("Invalid transaction status response for txnId={}", transactionStatusRequest.getTxnId());
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
LOGGER.info("Transaction status received: {}", generalResp.getStatus());
TransactionMaster transactionMaster = modelMapper.map(responseWrapper.getTransactionDAO(),
TransactionMaster.class);
transactionMaster.setTxnId(txnId);
transactionMaster.setReqId(transactionStatusRequest.getReqId());
transactionMaster.setMid(transactionStatusRequest.getMid());
transactionMaster.setClientUserId(transactionStatusRequest.getClientUserId());
EntityRelationshipUtil.linkTransactionMasterRelationships(transactionMaster);
transactionMasterRepository.save(transactionMaster);
LOGGER.info("TransactionMaster saved successfully for txnId={}", txnId);
AgentFundTransfer agentFundTransfer = modelMapper.map(responseWrapper.getAgentFundTransfer(),
AgentFundTransfer.class);
agentFundTransfer.setTxnId(txnId);
agentFundTransfer.setReqId(transactionStatusRequest.getReqId());
agentFundTransfer.setTransactionMasterId(transactionMaster.getBankTxnId());
agentFundTransferRepository.save(agentFundTransfer);
LOGGER.info("AgentFundTransfer entry saved successfully for txnId={}", txnId);
ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END (internal) | checkTxnId={} | reqId={} | status={}", apiName, txnId, requestId,
generalResp.getStatus());
return responseModel;
} catch (JsonProcessingException e) {
LOGGER.error("Error parsing transaction status JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException("Invalid JSON received from API");
} }
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
TransactionDTO txDao = responseWrapper.getTransactionDAO();
TransactionMaster transactionMaster = modelMapper.map(txDao,
TransactionMaster.class);
LOGGER.info("INBOUND TRANSACTION ID:{}",inboundLog.getTxnId());
TransactionMaster
existingTransaction=transactionMasterRepository.findByInitiatorTxnId(
inboundLog.getTxnId());
LOGGER.info("existingTransaction :{}",existingTransaction);
if(existingTransaction != null) {
LOGGER.info("Existing TransactionMaster found for bankTxnId");
// Merge incoming non-null fields into the existing DB entity so we preserve DB values
helper.mergeNonNullFields(transactionMaster, existingTransaction);
transactionMaster = existingTransaction;
}
transactionMaster.setCheckerTxnId(checkerTxnId);
EntityRelationshipUtil.linkTransactionMasterRelationships(transactionMaster);
AgentFundTransfer agentFundTransfer = null;
if (responseWrapper.getAgentFundTransfer() != null) {
agentFundTransfer = modelMapper.map(responseWrapper.getAgentFundTransfer(), AgentFundTransfer.class);
} else {
agentFundTransfer = new AgentFundTransfer();
}
agentFundTransfer.setTxnId(transactionMaster.getTxnId());
agentFundTransfer.setReqId(transactionStatusRequest.getReqId());
agentFundTransfer.setTransactionMasterId(transactionMaster.getBankTxnId());
transactionMasterRepository.save(transactionMaster);
if (agentFundTransfer != null) { agentFundTransferRepository.save(agentFundTransfer); }
LOGGER.info("Persisted TransactionMaster and AgentFundTransfer for txnId={} bankTxnId={}"
, transactionMaster.getTxnId(), transactionMaster.getBankTxnId());
ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END (internal) | checkTxnId={} | reqId={} | status={}", apiName, checkerTxnId, requestId, generalResp.getStatus());
return responseModel;
} }
@Override @Override
public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest) { public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest) {
helper.validateMerchant(transactionStatusRequest.getMid()); helper.validateMerchant(transactionStatusRequest.getMid());
String txnID = helper.generateTxnId(15); String txnID = helper.generateTxnId(15);
String apiName = BBPSConstant.API_CHECK_TRANSACTION_PUBLIC; String apiName = BBPSConstant.API_CHECK_TRANSACTION_PUBLIC;
String requestId = transactionStatusRequest.getReqId(); String requestId = transactionStatusRequest.getReqId();
LOGGER.info("[{}] START (public) | reqId={} | mid={} | checkBankTxnId={}", apiName, requestId, LOGGER.info("[{}] START (public) | reqId={} | mid={} | checkBankTxnId={}", apiName, requestId,
transactionStatusRequest.getMid(), transactionStatusRequest.getTxnId()); transactionStatusRequest.getMid(), transactionStatusRequest.getTxnId());
String requestJson = helper.convertObjectToJson(transactionStatusRequest); String requestJson = helper.convertObjectToJson(transactionStatusRequest);
String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId();
LOGGER.info("Checking transaction status (public) for txnId={} and reqId{}", LOGGER.info("Checking transaction status (public) for txnId={} and reqId={}",
transactionStatusRequest.getTxnId(), requestId); transactionStatusRequest.getTxnId(), requestId);
// First, attempt to find an existing TransactionMaster by initiatorTxnId and update it BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID);
String initiatorTxnId = transactionStatusRequest.getTxnId(); HttpHeaders headers = helper.buildHeaders(headersData);
if (initiatorTxnId != null && !initiatorTxnId.isBlank()) {
// logic intentionally left minimal here — any runtime exception should bubble
// to the global exception handler so failures are visible and handled centrally.
}
LOGGER.info("Falling back to external BBPS status check URL={}", url); InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName,
transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson);
LOGGER.debug("Inbound log created for public status check | reqId={} | txnId={}", requestId, txnID);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET,
null);
LOGGER.debug("Outbound log created for public status check | reqId={} | txnId={}", requestId, txnID);
BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); Map<String, String> responseMap = httpService.getRequest(url, headers);
HttpHeaders headers = helper.buildHeaders(headersData); String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Transaction status response data: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for public status check | reqId={} | txnId={}", requestId, txnID);
InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName, try {
transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson); Map<String, Object> dataMap = objectMapper.readValue(responseData,
LOGGER.debug("Inbound log created for public status check | reqId={} | txnId={}", requestId, txnID); new TypeReference<Map<String, Object>>() {
OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET, });
null); if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
LOGGER.debug("Outbound log created for public status check | reqId={} | txnId={}", requestId, txnID); helper.updateInboundLogFailure(inboundLog, responseData,
String.valueOf(dataMap.get(BBPSConstant.RESPONSE_STATUS_KEY)));
LOGGER.warn("Invalid transaction status response for txnId={} from BBPS",
transactionStatusRequest.getTxnId());
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
Map<String, String> responseMap = httpService.getRequest(url, headers); TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); TransactionStatusResponse.class);
LOGGER.info("Transaction status response data: {}", responseData); GeneralResp generalResp = responseWrapper.getGeneralResp();
helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for public status check | reqId={} | txnId={}", requestId, txnID);
Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog); String status = generalResp.getStatus();
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { LOGGER.info("Current transaction status: {}", status);
helper.updateInboundLogFailure(inboundLog, responseData,
String.valueOf(dataMap.get(BBPSConstant.RESPONSE_STATUS_KEY)));
LOGGER.warn("Invalid transaction status response for txnId={} from BBPS",
transactionStatusRequest.getTxnId());
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
TransactionStatusResponse.class); Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
GeneralResp generalResp = responseWrapper.getGeneralResp();
String status = generalResp.getStatus(); helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("Current transaction status: {}", status); LOGGER.info("[{}] END (public) | reqId={} | txnId={} | status={}", apiName, requestId, txnID,
generalResp.getStatus());
return responseModel;
// --- NEW: attempt to persist/update TransactionMaster so DB reflects new status/bankTxnId --- } catch (JsonProcessingException e) {
TransactionDTO txDao = responseWrapper.getTransactionDAO(); LOGGER.error("Error parsing responseData JSON: {}", e.getMessage(), e);
TransactionMaster updatedEntity = null; helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException("Invalid JSON received from API");
}
ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), }
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END (public) | reqId={} | txnId={} | status={}", apiName, requestId, txnID,
generalResp.getStatus());
return responseModel;
}
@Override @Override
public ResponseModel checkTrasactionStatusByClientReqId( public ResponseModel checkTrasactionStatusByClientReqId(
@@ -334,92 +345,68 @@ public class PaymentServiceImpl implements PaymentService {
LOGGER.info("[{}] START | reqId={} | mid={} | clientReqId={}", apiName, requestId, LOGGER.info("[{}] START | reqId={} | mid={} | clientReqId={}", apiName, requestId,
transactionStatusRequest.getMid(), clientReqId); transactionStatusRequest.getMid(), clientReqId);
// look up TransactionMaster(s) by clientReqId instead of PaymentInitiate Optional<PaymentInitiate> paymentInitiate = paymentIntiateRepository.findByReqId(clientReqId);
List<TransactionMaster> transactionMasters = transactionMasterRepository.findByClientReqId(clientReqId); if (paymentInitiate.isEmpty()) {
if (transactionMasters == null || transactionMasters.isEmpty()) { throw new CustomException(BBPSConstant.NO_BILL_FOUND);
throw new CustomException(
BBPSConstant.NO_BILL_FOUND, // message // status
BBPSConstant.FAILED_STATUS,
BBPSConstant.HTTP_NOT_FOUND, // statusCode
"Bill not found in database for given clientReqId"
);
} }
// prefer a TransactionMaster with a real bankTxnId (not a placeholder)
TransactionMaster chosen = null;
for (TransactionMaster t : transactionMasters) {
if (t.getBankTxnId() != null && !t.getBankTxnId().startsWith("PENDING_") && !t.getBankTxnId().startsWith("INVALID_") && !t.getBankTxnId().startsWith("INVALID_JSON_")) {
chosen = t;
break;
}
}
if (chosen == null) chosen = transactionMasters.get(0);
String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_BY_CLIENT_REQ_ID_URL String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_BY_CLIENT_REQ_ID_URL
+ chosen.getTxnId(); + paymentInitiate.get().getTxnId();
BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID);
HttpHeaders headers = helper.buildHeaders(headersData); HttpHeaders headers = helper.buildHeaders(headersData);
InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName, InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName,
transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson); transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson);
LOGGER.debug("Inbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID); LOGGER.debug("Inbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET, OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET,
null); null);
LOGGER.debug("Outbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID); LOGGER.debug("Outbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID);
LOGGER.info("Calling BBPS Transaction Status API URL={}", url);
Map<String, String> responseMap = httpService.getRequest(url, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Transaction Status Response: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for clientReqId check | reqId={} | txnId={}", requestId, txnID);
Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog);
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE);
LOGGER.warn("Invalid transaction status response for clientReqId={} | Response={}", clientReqId,
responseData);
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
LOGGER.info("Transaction Status for clientReqId={} => {}", clientReqId, generalResp.getStatus());
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END | reqId={} | clientReqId={} | status={}", apiName, requestId, clientReqId,
generalResp.getStatus());
return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
}
private String ensureTxnId(String txnId) {
if (txnId == null || txnId.isBlank()) {
return helper.generateTxnId(15);
}
return txnId;
}
private Map<String, Object> parseJsonToMap(String responseData, InboundTraceLog inboundLog) {
try { try {
return objectMapper.readValue(responseData, new TypeReference<Map<String, Object>>() {}); LOGGER.info("Calling BBPS Transaction Status API URL={}", url);
Map<String, String> responseMap = httpService.getRequest(url, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Transaction Status Response: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for clientReqId check | reqId={} | txnId={}", requestId, txnID);
Map<String, Object> dataMap = objectMapper.readValue(responseData,
new TypeReference<Map<String, Object>>() {
});
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE);
LOGGER.warn("Invalid transaction status response for clientReqId={} | Response={}", clientReqId,
responseData);
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
LOGGER.info("Transaction Status for clientReqId={} => {}", clientReqId, generalResp.getStatus());
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END | reqId={} | clientReqId={} | status={}", apiName, requestId, clientReqId,
generalResp.getStatus());
return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing JSON response: {}", e.getMessage(), e); LOGGER.error("Error parsing JSON in transaction status API: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
throw new CustomException("Invalid JSON received from API");
} }
} }
@@ -87,6 +87,7 @@ public class PaymentStartServiceImpl implements PaymentStartService {
String txnId = helper.generateTxnId(15); String txnId = helper.generateTxnId(15);
LOGGER.info("MID IS:{}",paymentMid); LOGGER.info("MID IS:{}",paymentMid);
LOGGER.info("[startIntentPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); LOGGER.info("[startIntentPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid());
helper.validateMerchant(request.getMid()); helper.validateMerchant(request.getMid());
// inbound log // inbound log
@@ -111,12 +112,8 @@ public class PaymentStartServiceImpl implements PaymentStartService {
LOGGER.info("[startIntentPayment] outbound payload prepared txnId={} orderNo={}", txnId, orderNo); LOGGER.info("[startIntentPayment] outbound payload prepared txnId={} orderNo={}", txnId, orderNo);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_INTENT, BBPSConstant.HTTP_POST, forwardJson); OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_INTENT, BBPSConstant.HTTP_POST, forwardJson);
PaymentStartRecord paymentRecord=bbpsMapperHelper.savePaymentStartRecord(request, null, txnId, orderNo, requestJson, null, null);
// call external API // call external API
Map<String, String> responseMap = httpService.postRequest(BBPSConstant.UAT_PAYMENT_URL_INTENT, forwardJson, null); Map<String, String> responseMap = httpService.postRequest(BBPSConstant.UAT_PAYMENT_URL_INTENT, forwardJson, null);
LOGGER.debug("[startIntentPayment] raw response map: {}", responseMap); LOGGER.debug("[startIntentPayment] raw response map: {}", responseMap);
int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)));
@@ -127,15 +124,12 @@ public class PaymentStartServiceImpl implements PaymentStartService {
if (httpCode != BBPSConstant.HTTP_OK_INT) { if (httpCode != BBPSConstant.HTTP_OK_INT) {
helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode));
LOGGER.error("[startIntentPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); LOGGER.error("[startIntentPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody);
paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR);
paymentStartRecordRepository.save(paymentRecord);
throw new CustomException(BBPSConstant.MSG_UPSTREAM_HTTP_ERROR, BBPSConstant.FAILED_STR, httpCode, respBody); throw new CustomException(BBPSConstant.MSG_UPSTREAM_HTTP_ERROR, BBPSConstant.FAILED_STR, httpCode, respBody);
} }
if (respBody == null || respBody.isEmpty()) { if (respBody == null || respBody.isEmpty()) {
helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR));
LOGGER.error("[startIntentPayment] empty response txnId={}", txnId); LOGGER.error("[startIntentPayment] empty response txnId={}", txnId);
paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR);
throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null);
} }
@@ -164,7 +158,8 @@ public class PaymentStartServiceImpl implements PaymentStartService {
// success: persist and return // success: persist and return
helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(outcomeCode)); helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(outcomeCode));
String responseType = BBPSConstant.RESPONSE_TYPE_INTENT; String responseType = (apiResponse.getResponseData() != null && apiResponse.getResponseData().getQrString() != null && !apiResponse.getResponseData().getQrString().isEmpty()) ? BBPSConstant.RESPONSE_TYPE_QR : BBPSConstant.RESPONSE_TYPE_JSON;
bbpsMapperHelper.savePaymentStartRecord(request, apiResponse, txnId, orderNo, requestJson, respBody, responseType); bbpsMapperHelper.savePaymentStartRecord(request, apiResponse, txnId, orderNo, requestJson, respBody, responseType);
LOGGER.info("[startIntentPayment] saved record txnId={} orderNo={} responseType={}", txnId, orderNo, responseType); LOGGER.info("[startIntentPayment] saved record txnId={} orderNo={} responseType={}", txnId, orderNo, responseType);
@@ -216,92 +211,88 @@ public class PaymentStartServiceImpl implements PaymentStartService {
return sent; return sent;
} }
@Override @Override
public ResponseModel startWebPayment(PaymentStartUserRequest request) { public ResponseModel startWebPayment(PaymentStartUserRequest request) {
String requestId = request.getReqId(); String requestId = request.getReqId();
String apiName = BBPSConstant.API_START_PAYMENT; String apiName = BBPSConstant.API_START_PAYMENT;
String txnId = helper.generateTxnId(15); String txnId = helper.generateTxnId(15);
LOGGER.info("[startWebPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid());
helper.validateMerchant(request.getMid()); LOGGER.info("[startWebPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid());
// inbound log helper.validateMerchant(request.getMid());
String requestJson = helper.convertObjectToJson(request);
InboundTraceLog inboundLog = // inbound log
String requestJson = helper.convertObjectToJson(request);
InboundTraceLog inboundLog =
helper.createInboundLog(requestId, txnId, apiName, request.getMid(), helper.createInboundLog(requestId, txnId, apiName, request.getMid(),
BBPSConstant.HTTP_POST, requestJson); BBPSConstant.HTTP_POST, requestJson);
LOGGER.debug("[startWebPayment] inbound log created txnId={} requestId={}", txnId, requestId); LOGGER.debug("[startWebPayment] inbound log created txnId={} requestId={}", txnId, requestId);
// Prepare request payload // Prepare request payload
PaymentDetailSentRequest sent = createPaymentSentRequest(request); PaymentDetailSentRequest sent = createPaymentSentRequest(request);
String orderNo = "ORD" + helper.generateTxnId(12); String orderNo = "ORD" + helper.generateTxnId(12);
sent.setOrderNo(orderNo); sent.setOrderNo(orderNo);
String plaintext = helper.convertObjectToJson(sent); String plaintext = helper.convertObjectToJson(sent);
String encKey = paymentEncryKey; String encKey = paymentEncryKey;
sent.setEnckey(encKey); sent.setEnckey(encKey);
LOGGER.debug("[startWebPayment] plaintext prepared txnId={} orderNo={}", txnId, orderNo); LOGGER.debug("[startWebPayment] plaintext prepared txnId={} orderNo={}", txnId, orderNo);
String encrypted = encryptionUtil.encryptMData(plaintext, encKey); String encrypted = encryptionUtil.encryptMData(plaintext, encKey);
LOGGER.info("[startWebPayment] payload encrypted txnId={} orderNo={}", txnId, orderNo); LOGGER.info("[startWebPayment] payload encrypted txnId={} orderNo={}", txnId, orderNo);
// ----------- BUILD x-www-form-urlencoded PAYLOAD ----------- // ----------- BUILD x-www-form-urlencoded PAYLOAD -----------
String formPayload = String formPayload =
"mid=" + URLEncoder.encode(paymentMid, StandardCharsets.UTF_8) + "mid=" + URLEncoder.encode(paymentMid, StandardCharsets.UTF_8) +
"&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8); "&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8);
LOGGER.info("[startWebPayment] Form Payload: {}", formPayload); LOGGER.info("[startWebPayment] Form Payload: {}", formPayload);
OutboundTraceLog outboundLog = OutboundTraceLog outboundLog =
helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_WEB, helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_WEB,
BBPSConstant.HTTP_POST, formPayload); BBPSConstant.HTTP_POST, formPayload);
LOGGER.debug("[startWebPayment] outbound log created txnId={} url={}", txnId, BBPSConstant.UAT_PAYMENT_URL_WEB); LOGGER.debug("[startWebPayment] outbound log created txnId={} url={}", txnId, BBPSConstant.UAT_PAYMENT_URL_WEB);
PaymentStartRecord paymentRecord= bbpsMapperHelper.savePaymentStartRecord(request, null, txnId, orderNo, requestJson, null, null);
// ----------- CALL API (FORM URL-ENCODED) ----------- // ----------- CALL API (FORM URL-ENCODED) -----------
Map<String, String> responseMap = httpService.postFormRequest(BBPSConstant.UAT_PAYMENT_URL_WEB, formPayload); Map<String, String> responseMap =
httpService.postFormRequest(BBPSConstant.UAT_PAYMENT_URL_WEB, formPayload);
LOGGER.info("[startWebPayment] Payment Start Response: {}", responseMap); LOGGER.info("[startWebPayment] Payment Start Response: {}", responseMap);
// parse http code and response body using centralized keys // parse http code and response body using centralized keys
int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)));
String respBody = responseMap.getOrDefault(BBPSConstant.RESPONSE_DATA_KEY, ""); String respBody = responseMap.getOrDefault(BBPSConstant.RESPONSE_DATA_KEY, "");
helper.updateOutboundLogResponse(outboundLog, respBody); helper.updateOutboundLogResponse(outboundLog, respBody);
LOGGER.debug("[startWebPayment] updated outbound log response txnId={} httpCode={}", txnId, httpCode); LOGGER.debug("[startWebPayment] updated outbound log response txnId={} httpCode={}", txnId, httpCode);
// ---- HTTP FAILURE ---- // ---- HTTP FAILURE ----
if (httpCode != BBPSConstant.HTTP_OK_INT) { if (httpCode != BBPSConstant.HTTP_OK_INT) {
helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode));
LOGGER.error("[startWebPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); LOGGER.error("[startWebPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody);
paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR); throw new CustomException("Upstream HTTP Error", BBPSConstant.FAILED_STR, httpCode, respBody);
paymentStartRecordRepository.save(paymentRecord); }
throw new CustomException("Upstream HTTP Error", BBPSConstant.FAILED_STR, httpCode, respBody);
}
// ---- EMPTY RESPONSE ---- // ---- EMPTY RESPONSE ----
if (respBody == null || respBody.isEmpty()) { if (respBody == null || respBody.isEmpty()) {
helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PG, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PG, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR));
LOGGER.error("[startWebPayment] empty response from PG txnId={}", txnId); LOGGER.error("[startWebPayment] empty response from PG txnId={}", txnId);
paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR); throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PG, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null);
paymentStartRecordRepository.save(paymentRecord); }
throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PG, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null);
}
// PG RETURNS HTML → NO JSON PARSING REQUIRED // PG RETURNS HTML → NO JSON PARSING REQUIRED
helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(BBPSConstant.HTTP_OK_INT)); helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(BBPSConstant.HTTP_OK_INT));
LOGGER.info("[startWebPayment] received HTML response txnId={} length={}", txnId, respBody.length()); LOGGER.info("[startWebPayment] received HTML response txnId={} length={}", txnId, respBody.length());
// Save record with HTML (persist original request and HTML response) // Save record with HTML (persist original request and HTML response)
String responseTypeWeb = BBPSConstant.RESPONSE_TYPE_WEB; String responseTypeWeb = BBPSConstant.RESPONSE_TYPE_HTML;
String rawResponseWeb = respBody == null ? "" : respBody; String rawResponseWeb = respBody == null ? "" : respBody;
// Try to extract useful fields from returned HTML (txnId, amount, mid) // Try to extract useful fields from returned HTML (txnId, amount, mid)
Map<String, String> parsed = parseHtmlResponse(rawResponseWeb); Map<String, String> parsed = parseHtmlResponse(rawResponseWeb);
// Build a minimal PaymentStartApiResponse so savePaymentStartRecord() can safely read fields // Build a minimal PaymentStartApiResponse so savePaymentStartRecord() can safely read fields
PaymentStartApiResponse apiResp = new PaymentStartApiResponse(); com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse apiResp = new com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse();
PaymentStartApiResponse.ResponseData rd = new PaymentStartApiResponse.ResponseData(); com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse.ResponseData rd = new com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse.ResponseData();
String parsedTxn = parsed.getOrDefault("txnId", ""); String parsedTxn = parsed.getOrDefault("txnId", "");
String parsedAmount = parsed.getOrDefault("amount", "0.00"); String parsedAmount = parsed.getOrDefault("amount", "0.00");
@@ -312,7 +303,7 @@ public class PaymentStartServiceImpl implements PaymentStartService {
rd.setAmount(parsedAmount != null && !parsedAmount.isEmpty() ? parsedAmount : "0.00"); rd.setAmount(parsedAmount != null && !parsedAmount.isEmpty() ? parsedAmount : "0.00");
rd.setMid(parsedMid); rd.setMid(parsedMid);
rd.setOrderNo(orderNo); rd.setOrderNo(orderNo);
rd.setQrString(BBPSConstant.RESPONSE_TYPE_WEB); rd.setQrString("HTML_RESPONSE");
rd.setCreatedOn(LocalDateTime.now().toString()); rd.setCreatedOn(LocalDateTime.now().toString());
apiResp.setResponseData(rd); apiResp.setResponseData(rd);
@@ -362,8 +353,14 @@ public class PaymentStartServiceImpl implements PaymentStartService {
LOGGER.info("[{}] Decrypted data for mid={}", apiName, mid); LOGGER.info("[{}] Decrypted data for mid={}", apiName, mid);
// Parse decrypted JSON (convert checked Jackson exception into unchecked CustomException so global handler can catch it) // Parse decrypted JSON
Map<String, Object> payloadMap = parseJsonToMap(decrypted, apiName); Map<String, Object> payloadMap;
try {
payloadMap = objectMapper.readValue(decrypted, new TypeReference<Map<String, Object>>(){});
} catch (JsonProcessingException e) {
LOGGER.error("[{}] Failed to parse decrypted callback JSON: {}", apiName, e.getMessage(), e);
return new ResponseModel(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, String.valueOf(BBPSConstant.STATUS_CODE_207), BBPSConstant.HTTP_OK_INT, null);
}
String txnId = String.valueOf(payloadMap.getOrDefault("txnId", payloadMap.getOrDefault("txn_id", ""))); String txnId = String.valueOf(payloadMap.getOrDefault("txnId", payloadMap.getOrDefault("txn_id", "")));
String orderNo = String.valueOf(payloadMap.getOrDefault("orderNo", payloadMap.getOrDefault("order_no", ""))); String orderNo = String.valueOf(payloadMap.getOrDefault("orderNo", payloadMap.getOrDefault("order_no", "")));
@@ -400,9 +397,12 @@ public class PaymentStartServiceImpl implements PaymentStartService {
return new ResponseModel(BBPSConstant.MSG_NO_MATCHING_RECORD, BBPSConstant.MERCHANT_ACK_NO_MATCH, BBPSConstant.HTTP_OK_INT, null); return new ResponseModel(BBPSConstant.MSG_NO_MATCHING_RECORD, BBPSConstant.MERCHANT_ACK_NO_MATCH, BBPSConstant.HTTP_OK_INT, null);
} }
// Update record with bank response and signature
record.setBankResponse(decrypted);
record.setSignature(signature); record.setSignature(signature);
// update txn status and callback timestamp
record.setTxnStatus(txnStatus); record.setTxnStatus(txnStatus);
record.setCallbackReceivedOn(LocalDateTime.now().toString()); record.setCallbackReceivedOn(java.time.LocalDateTime.now().toString());
String merchantStatus; String merchantStatus;
if (!signatureMatches) { if (!signatureMatches) {
@@ -437,44 +437,30 @@ public class PaymentStartServiceImpl implements PaymentStartService {
return new ResponseModel(BBPSConstant.MSG_CALLBACK_PROCESSED, merchantStatus, BBPSConstant.HTTP_OK_INT, null); return new ResponseModel(BBPSConstant.MSG_CALLBACK_PROCESSED, merchantStatus, BBPSConstant.HTTP_OK_INT, null);
} }
public Map<String, String> parseHtmlResponse(String html) { public Map<String, String> parseHtmlResponse(String html) {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
Document doc = Jsoup.parse(html); try {
Document doc = Jsoup.parse(html);
Element txnId = doc.selectFirst("input[name=txnId]"); Element txnId = doc.selectFirst("input[name=txnId]");
Element amount = doc.selectFirst("input[name=amount]"); Element amount = doc.selectFirst("input[name=amount]");
Element mid = doc.selectFirst("input[name=mid]"); Element mid = doc.selectFirst("input[name=mid]");
if (txnId != null) map.put("txnId", txnId.attr("value")); if (txnId != null) map.put("txnId", txnId.attr("value"));
if (amount != null) map.put("amount", amount.attr("value")); if (amount != null) map.put("amount", amount.attr("value"));
if (mid != null) map.put("mid", mid.attr("value")); if (mid != null) map.put("mid", mid.attr("value"));
LOGGER.debug("[parseHtmlResponse] parsed txnId={} amount={} mid={}", map.get("txnId"), map.get("amount"), map.get("mid")); LOGGER.debug("[parseHtmlResponse] parsed txnId={} amount={} mid={}", map.get("txnId"), map.get("amount"), map.get("mid"));
} catch (Exception e) {
LOGGER.error("[parseHtmlResponse] error parsing HTML: {}", e.getMessage(), e);
}
return map; return map;
} }
// Helper: parse JSON string into Map and rethrow Jackson checked exceptions as CustomException (unchecked)
private Map<String, Object> parseJsonToMap(String json, String apiName) {
if (json == null || json.trim().isEmpty()) {
LOGGER.error("[{}] Decrypted callback JSON is empty or null (length={})", apiName, json == null ? 0 : json.length());
throw new CustomException(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, json);
}
// Log a short preview (avoid full sensitive data in logs)
int previewLen = Math.min(120, json.length());
String preview = json.substring(0, previewLen) + (json.length() > previewLen ? "..." : "");
LOGGER.debug("[{}] parsing decrypted JSON preview (len={}): {}", apiName, json.length(), preview);
try {
return objectMapper.readValue(json, new TypeReference<Map<String, Object>>(){});
} catch (Exception e) {
// Jackson throws JsonProcessingException / IOException; catch any parsing error and rethrow as unchecked
LOGGER.error("[{}] Failed to parse decrypted callback JSON (len={}) error={}", apiName, json.length(), e.getMessage(), e);
throw new CustomException(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, json);
}
}
} }
@@ -23,6 +23,7 @@ public class PollBackServiceImpl implements PollBackService {
this.helper = helper; this.helper = helper;
} }
@Override @Override
public ResponseModel managePollBack(PollBackRequest request) { public ResponseModel managePollBack(PollBackRequest request) {
@@ -67,7 +67,7 @@ public class TxnHistoryServiceImpl implements TxnHistoryService {
} else { } else {
Pageable pageable = PageRequest.of(paymentHistoryRequest.getPageNo(), Pageable pageable = PageRequest.of(paymentHistoryRequest.getPageNo(),
paymentHistoryRequest.getPageSize()); paymentHistoryRequest.getPageSize());
dataResult = transactionMasterRepository.findByMidAndUserId(mid, clientUserId, pageable); dataResult = transactionMasterRepository.findByMidOrClientUserId(mid, clientUserId, pageable);
} }
// List<TransactionMaster> dataResult = // List<TransactionMaster> dataResult =
@@ -109,6 +109,7 @@ public class TxnHistoryServiceImpl implements TxnHistoryService {
String mid = request.getMid(); String mid = request.getMid();
String clientUserId = request.getClientUserId(); String clientUserId = request.getClientUserId();
log.info("[{}] START | reqId={} | mid={} | userId={}", apiName, requestId, mid, clientUserId); log.info("[{}] START | reqId={} | mid={} | userId={}", apiName, requestId, mid, clientUserId);
String txnId = helper.generateTxnId(15); String txnId = helper.generateTxnId(15);
String fullUrl = "this api fetch data from DB not BBPS url !"; String fullUrl = "this api fetch data from DB not BBPS url !";
log.info("[{}] Full URL={}", apiName, fullUrl); log.info("[{}] Full URL={}", apiName, fullUrl);
@@ -18,10 +18,11 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT {
public static final Long PENDING_CODE = 211L; public static final Long PENDING_CODE = 211L;
public static final Long FRAUD_CODE = 1001L; public static final Long FRAUD_CODE = 1001L;
public static final int BADREQUEST = 400; public static final int BADREQUEST = 400;
public static final int TO_MANY_REQUEST=429;
public static final String SUCCESS_STATUS = "SUCCESS"; public static final String SUCCESS_STATUS = "success";
public static final String FAILED_STATUS = "FAILED"; public static final String FAILED_STATUS = "failed";
public static final String PENDING_STATUS = "PENDING"; public static final String PENDING_STATUS = "pending";
public static final String SENT_TO_PROCESSOR_STATUS = "txn sent to processor"; public static final String SENT_TO_PROCESSOR_STATUS = "txn sent to processor";
public static final String SUCCESS_REMARK = "200-Transaction process successfully"; public static final String SUCCESS_REMARK = "200-Transaction process successfully";
@@ -130,7 +131,6 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT {
public static final int HTTP_OK_INT = 200; public static final int HTTP_OK_INT = 200;
public static final int HTTP_NOT_FOUND = 404; public static final int HTTP_NOT_FOUND = 404;
public static final int HTTP_INTERNAL_SERVER_ERROR = 500; public static final int HTTP_INTERNAL_SERVER_ERROR = 500;
public static final int HTTP_TO_MANY_REQUEST=429;
// Response and helper keys // Response and helper keys
public static final String HTTP_SERVICE_ERROR_PREFIX = "HttpService Error"; public static final String HTTP_SERVICE_ERROR_PREFIX = "HttpService Error";
@@ -147,6 +147,7 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT {
public static final String UAT_PAYMENT_URL_WEB = "https://uat.godemo.in/api/UATIntegration/uat/payin/paymentrequest"; public static final String UAT_PAYMENT_URL_WEB = "https://uat.godemo.in/api/UATIntegration/uat/payin/paymentrequest";
public static final String UAT_PAYMENT_URL_INTENT = "https://uat.godemo.in/api/UATIntegration/paymentrequest/seamless"; public static final String UAT_PAYMENT_URL_INTENT = "https://uat.godemo.in/api/UATIntegration/paymentrequest/seamless";
public static final String RESP_CODE_KEY = "respCode"; // key used by HttpService responses public static final String RESP_CODE_KEY = "respCode"; // key used by HttpService responses
public static final String RESPONSE_TYPE_HTML = "HTML"; public static final String RESPONSE_TYPE_HTML = "HTML";
public static final String RESPONSE_TYPE_QR = "QR"; public static final String RESPONSE_TYPE_QR = "QR";
public static final String RESPONSE_TYPE_JSON = "JSON"; public static final String RESPONSE_TYPE_JSON = "JSON";
@@ -195,9 +196,6 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT {
public static final String MSG_EMPTY_RESPONSE_PG = "Empty response from PG"; public static final String MSG_EMPTY_RESPONSE_PG = "Empty response from PG";
public static final String MSG_INVALID_JSON_FROM_API = "Invalid JSON received from payment API"; public static final String MSG_INVALID_JSON_FROM_API = "Invalid JSON received from payment API";
public static final String MSG_TXN_PROCESSING = "Transaction processing"; public static final String MSG_TXN_PROCESSING = "Transaction processing";
// User-facing message for malformed responses from upstream services (used with status code 603)
// Short, non-leaking and actionable message
public static final String MSG_INVALID_JSON_FROM_UPSTREAM = "Temporary upstream data error. Please try again later.";
public static final String MSG_WARNING_FROM_MERCHANT = "Warning from merchant"; public static final String MSG_WARNING_FROM_MERCHANT = "Warning from merchant";
public static final String MSG_FRAUD_REJECTED = "Fraud Rejected"; public static final String MSG_FRAUD_REJECTED = "Fraud Rejected";
public static final String MSG_DECRYPTION_FAILED = "Decryption failed"; public static final String MSG_DECRYPTION_FAILED = "Decryption failed";
@@ -222,10 +220,5 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT {
public static final String INTERNALSERVER_ERROR_STR = "600"; public static final String INTERNALSERVER_ERROR_STR = "600";
public static final String USER_UNAUTHORIZED_STR = "401"; public static final String USER_UNAUTHORIZED_STR = "401";
public static final String OK_STR = "200"; public static final String OK_STR = "200";
public static final String RESPONSE_TYPE_INTENT = "INTENT";
public static final String RESPONSE_TYPE_WEB = "WEB";
// Newly added constant for invalid JSON status code
} }
@@ -1,7 +1,6 @@
package com.billpaymentservice.bbps.util; package com.billpaymentservice.bbps.util;
public class BBPS_RESPONSE_COD_CONSTANT { public class BBPS_RESPONSE_COD_CONSTANT {
public static final int INVALID_JSON_STATUS_CODE = 603; public static final int USER_IS_DELTED_IN_DB =602;
} }
@@ -2,7 +2,6 @@ package com.billpaymentservice.bbps.util;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
@@ -28,9 +27,6 @@ import com.billpaymentservice.bbps.dto.BillCustomerParamsDTO;
import com.billpaymentservice.bbps.dto.BillerIconDTO; import com.billpaymentservice.bbps.dto.BillerIconDTO;
import com.billpaymentservice.bbps.dto.CheckClientBillerResponseDTO; import com.billpaymentservice.bbps.dto.CheckClientBillerResponseDTO;
import com.billpaymentservice.bbps.dto.ComplaintHistoryDTO; import com.billpaymentservice.bbps.dto.ComplaintHistoryDTO;
import com.billpaymentservice.bbps.dto.ComplaintResponseDto;
import com.billpaymentservice.bbps.dto.ComplaintStatusDetailsDto;
import com.billpaymentservice.bbps.dto.ComplaintStatusDto;
import com.billpaymentservice.bbps.dto.ContactMaster; import com.billpaymentservice.bbps.dto.ContactMaster;
import com.billpaymentservice.bbps.dto.CustomerParamDAO; import com.billpaymentservice.bbps.dto.CustomerParamDAO;
import com.billpaymentservice.bbps.dto.CustomerParamGroupsDTO; import com.billpaymentservice.bbps.dto.CustomerParamGroupsDTO;
@@ -42,10 +38,11 @@ import com.billpaymentservice.bbps.dto.TicketDetailsDTO;
import com.billpaymentservice.bbps.dto.TicketDispositionsDTO; import com.billpaymentservice.bbps.dto.TicketDispositionsDTO;
import com.billpaymentservice.bbps.dto.TransactionHistoryMasterDTO; import com.billpaymentservice.bbps.dto.TransactionHistoryMasterDTO;
import com.billpaymentservice.bbps.dto.TransactionHistoryParticularDetailsDTO; import com.billpaymentservice.bbps.dto.TransactionHistoryParticularDetailsDTO;
import com.billpaymentservice.bbps.dto.ViewAllComplaintDto;
import com.billpaymentservice.bbps.dto.request.BillFetchByBillIdRequest; import com.billpaymentservice.bbps.dto.request.BillFetchByBillIdRequest;
import com.billpaymentservice.bbps.dto.request.BillPaymentRequest; import com.billpaymentservice.bbps.dto.request.BillPaymentRequest;
import com.billpaymentservice.bbps.dto.request.ComplaintRequestDTO; import com.billpaymentservice.bbps.dto.request.ComplaintRequestDTO;
import com.billpaymentservice.bbps.dto.request.FetchBillRequest;
import com.billpaymentservice.bbps.dto.request.FetchBillValidatestRequest;
import com.billpaymentservice.bbps.dto.request.GetTicketLatestStausRequest; import com.billpaymentservice.bbps.dto.request.GetTicketLatestStausRequest;
import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest; import com.billpaymentservice.bbps.dto.request.PaymentStartUserRequest;
import com.billpaymentservice.bbps.dto.response.AmountOptionsResponse; import com.billpaymentservice.bbps.dto.response.AmountOptionsResponse;
@@ -61,6 +58,8 @@ import com.billpaymentservice.bbps.dto.response.ViewAllComplaintsResponse;
import com.billpaymentservice.bbps.dto.response.ViewAllComplaintsTicketsResponse; import com.billpaymentservice.bbps.dto.response.ViewAllComplaintsTicketsResponse;
import com.billpaymentservice.bbps.dto.response.ViewComplainByIdResponse; import com.billpaymentservice.bbps.dto.response.ViewComplainByIdResponse;
import com.billpaymentservice.bbps.dto.sentRequest.BillPaymentForwardRequest; import com.billpaymentservice.bbps.dto.sentRequest.BillPaymentForwardRequest;
import com.billpaymentservice.bbps.dto.sentRequest.FetchBillForwardRequest;
import com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest;
import com.billpaymentservice.bbps.dto.sentRequest.FetchComplaintForwardRequest; import com.billpaymentservice.bbps.dto.sentRequest.FetchComplaintForwardRequest;
import com.billpaymentservice.bbps.dto.sentRequest.FetchTicketLatestStatusRequest; import com.billpaymentservice.bbps.dto.sentRequest.FetchTicketLatestStatusRequest;
import com.billpaymentservice.bbps.entites.AdditionalInfoBill; import com.billpaymentservice.bbps.entites.AdditionalInfoBill;
@@ -120,6 +119,8 @@ import com.billpaymentservice.bbps.repository.ReOpenComplaintRepository;
import com.billpaymentservice.bbps.repository.TicketDispositionRepository; import com.billpaymentservice.bbps.repository.TicketDispositionRepository;
import com.billpaymentservice.bbps.repository.ViewAllComplainTicketDetailRepository; import com.billpaymentservice.bbps.repository.ViewAllComplainTicketDetailRepository;
import jakarta.persistence.Column;
@Component @Component
public class BillerMapperHelper { public class BillerMapperHelper {
@@ -575,6 +576,87 @@ public class BillerMapperHelper {
} }
} }
public Map<String, Object> getComplaintStatusJson() {
Map<String, Object> complaints = new HashMap<>();
complaints.put("assignedTo", "XYZ Bank");
complaints.put("complaintDescr", "Testing complaint");
complaints.put("complaintDispos", "Transaction Successful, account not updated");
complaints.put("complaintId", "2dfecc3c-9114-4094-9aa8-ff26f03a9bfd");
complaints.put("complaintNpciId", "FE4123109489834");
complaints.put("complaintRaisedFrom", "agent");
complaints.put("complaintType", "npciComplaint");
complaints.put("createdEpochDate", 1681905485);
complaints.put("creationDate", "2023-04-19T17:28:05.508679Z");
complaints.put("modifiedDate", "2023-04-19T17:28:10.001432Z");
complaints.put("modifiedEpochDate", 1681905490);
complaints.put("refId", "mqcXfZs2XMkRKQfpIwoSYQDnIr231091728");
complaints.put("status", "COMPLAINT_OPEN_SUCCESS");
complaints.put("tenantId", "FE41");
complaints.put("txnId", "24fe190c-42c9-4cb5-9d36-965839b0cb5f");
complaints.put("txnNpciId", "FE4131090DAI26Y0mAxl");
complaints.put("userId", "FE41FE42BNK519012345");
complaints.put("agentId", "FE41FE42BNK519012345");
Map<String, Object> finalJson = new HashMap<>();
finalJson.put("complaints", complaints);
return finalJson;
}
public Map<String, Object> getDummyReopenComplaintJson() {
Map<String, Object> complaints = new HashMap<>();
complaints.put("assignedTo", "Federal Bank");
complaints.put("complaintDescr", "Issue not yet solved - reopening");
complaints.put("complaintDispos", "Transaction Successful, account not updated");
complaints.put("complaintId", "809ec4c7-d81b-4d2b-9c66-3011a6fae714");
complaints.put("complaintNpciId", "FE4123109776644");
complaints.put("complaintRaisedFrom", "agent");
complaints.put("complaintType", "npciComplaint");
complaints.put("createdEpochDate", 1681893776);
complaints.put("creationDate", "2023-04-19T14:12:56.329935Z");
complaints.put("modifiedDate", "2023-04-19T14:19:44.891423Z");
complaints.put("modifiedEpochDate", 1681894184);
complaints.put("refId", "7sQwBamvWFyLxhK84dYgGmV24r831091419");
complaints.put("status", "COMPLAINT_REOPEN_SUCCESS");
complaints.put("tenantId", "FE41");
complaints.put("txnId", "155a4a71-141f-40fd-b772-9d0e020963e8");
complaints.put("txnNpciId", "FE413045Hl3L90ZWg59o");
complaints.put("userId", "FE41FE42BNK519012345");
complaints.put("agentId", "FE41FE42BNK519012345");
Map<String, Object> finalJson = new HashMap<>();
finalJson.put("complaints", complaints);
return finalJson;
}
public Map<String, Object> getDummyClosureComplaintJson() {
Map<String, Object> complaints = new HashMap<>();
complaints.put("complaintDescr", "Closing complaint Issue resolved");
complaints.put("complaintDispos", "Transaction Successful, account not updated");
complaints.put("complaintId", "809ec4c7-d81b-4d2b-9c66-3011a6fae714");
complaints.put("complaintNpciId", "FE4123109776644");
complaints.put("complaintRaisedFrom", "agent");
complaints.put("complaintType", "npciComplaint");
complaints.put("createdEpochDate", 1681893776);
complaints.put("creationDate", "2023-04-19T14:12:56.329935Z");
complaints.put("modifiedDate", "2023-04-19T14:13:32.25255Z");
complaints.put("modifiedEpochDate", 1681893812);
complaints.put("refId", "hHfEKYZExIpxNEV65HMQZ2Lh0DA31091413");
complaints.put("status", "COMPLAINT_CLOSURE_REQUEST_SUCCESS"); // removed spaces
complaints.put("tenantId", "FE41");
complaints.put("txnId", "155a4a71-141f-40fd-b772-9d0e020963e8");
complaints.put("txnNpciId", "FE413045Hl3L90ZWg59o");
complaints.put("userId", "FE41FE42BNK519012345");
complaints.put("agentId", "FE41FE42BNK519012345");
Map<String, Object> finalJson = new HashMap<>();
finalJson.put("complaints", complaints);
return finalJson;
}
public String saveComplainStatusDetail(ViewComplainByIdResponse responseWrapper, String reqId, String txnId, public String saveComplainStatusDetail(ViewComplainByIdResponse responseWrapper, String reqId, String txnId,
String userId) { String userId) {
@@ -637,8 +719,8 @@ public class BillerMapperHelper {
parent.setActionType(responseWrapper.getActionType()); parent.setActionType(responseWrapper.getActionType());
parent.setUserId(responseWrapper.getUserId()); parent.setUserId(responseWrapper.getUserId());
// Map incoming DTO date strings into Auditable LocalDateTime fields // Map incoming DTO date strings into Auditable LocalDateTime fields
Date parentCreatedOn = parseToDate(responseWrapper.getCreationDate()); LocalDateTime parentCreatedOn = parseToLocalDateTime(responseWrapper.getCreationDate());
Date parentUpdatedOn = parseToDate(responseWrapper.getModifiedDate()); LocalDateTime parentUpdatedOn = parseToLocalDateTime(responseWrapper.getModifiedDate());
parent.setCreatedEpochDate(responseWrapper.getCreatedEpochDate()); parent.setCreatedEpochDate(responseWrapper.getCreatedEpochDate());
parent.setModifiedEpochDate(responseWrapper.getModifiedEpochDate()); parent.setModifiedEpochDate(responseWrapper.getModifiedEpochDate());
parent.setReqId(reqId); parent.setReqId(reqId);
@@ -818,8 +900,9 @@ public class BillerMapperHelper {
} }
public void saveOrUpdateBillData(BillDataResponse responseWrapper, public void saveOrUpdateBillData(BillDataResponse responseWrapper,
BillFetchByBillIdRequest billFetchByBillIdRequest, String txnId, String reqId, String userId,BillMaster billMaster) { BillFetchByBillIdRequest billFetchByBillIdRequest, String txnId, String reqId, String userId) {
BillMaster billMaster = new BillMaster();
billMaster.setBillId(responseWrapper.getBillId()); billMaster.setBillId(responseWrapper.getBillId());
billMaster.setRefId(responseWrapper.getRefId()); billMaster.setRefId(responseWrapper.getRefId());
@@ -879,9 +962,9 @@ public class BillerMapperHelper {
// DATES // DATES
// Map incoming DTO date strings into Auditable LocalDateTime fields // Map incoming DTO date strings into Auditable LocalDateTime fields
Date bmCreatedOn = parseToDate(responseWrapper.getCreationDate()); LocalDateTime bmCreatedOn = parseToLocalDateTime(responseWrapper.getCreationDate());
Date bmUpdatedOn = parseToDate(responseWrapper.getModifiedDate()); LocalDateTime bmUpdatedOn = parseToLocalDateTime(responseWrapper.getModifiedDate());
if (bmUpdatedOn != null) { if (bmUpdatedOn != null) {
billMaster.setUpdatedDateTime(bmUpdatedOn); billMaster.setUpdatedDateTime(bmUpdatedOn);
} }
@@ -891,23 +974,29 @@ public class BillerMapperHelper {
billMasterRepository.save(billMaster); billMasterRepository.save(billMaster);
} }
// Helper to parse incoming ISO date/time strings (with optional offset) into // Helper to parse incoming ISO date/time strings (with optional offset) into LocalDateTime
// LocalDateTime private LocalDateTime parseToLocalDateTime(String dateStr) {
private Date parseToDate(String dateStr) { if (dateStr == null || dateStr.isBlank()) {
try { return null;
// example format: 2025-12-17T14:30:45 }
LocalDateTime ldt = LocalDateTime.parse(dateStr); try {
return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); // Try parsing with offset first (e.g., 2023-04-19T17:28:05.508679Z or with +05:30)
} catch (Exception e) { OffsetDateTime odt = OffsetDateTime.parse(dateStr);
return null; return odt.toLocalDateTime();
} } catch (Exception e) {
try {
// Fallback to LocalDateTime without offset
return LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
} catch (Exception ex) {
LOGGER.warn("Unable to parse date string to LocalDateTime: {}", dateStr);
return null;
}
}
} }
// Convert TransactionMaster to master DTO // Convert TransactionMaster to master DTO
public TransactionHistoryMasterDTO toDTO(TransactionMaster transactionMaster) { public TransactionHistoryMasterDTO toDTO(TransactionMaster transactionMaster) {
if (transactionMaster == null) if (transactionMaster == null) return null;
return null;
TransactionHistoryMasterDTO dto = new TransactionHistoryMasterDTO(); TransactionHistoryMasterDTO dto = new TransactionHistoryMasterDTO();
dto.setBankTxnId(transactionMaster.getBankTxnId()); dto.setBankTxnId(transactionMaster.getBankTxnId());
dto.setTxnId(transactionMaster.getTxnId()); dto.setTxnId(transactionMaster.getTxnId());
@@ -944,22 +1033,20 @@ public class BillerMapperHelper {
dto.setPaymentInformation(transactionMaster.getPaymentInformation()); dto.setPaymentInformation(transactionMaster.getPaymentInformation());
dto.setPlanId(transactionMaster.getPlanId()); dto.setPlanId(transactionMaster.getPlanId());
dto.setPlanType(transactionMaster.getPlanType()); dto.setPlanType(transactionMaster.getPlanType());
dto.setCreationDate(transactionMaster.getCreationDateBank()); dto.setCreationDate(transactionMaster.getCreationDate());
dto.setModifiedDate(transactionMaster.getModifiedDateBank()); dto.setModifiedDate(transactionMaster.getModifiedDate());
dto.setCreatedEpochDate(transactionMaster.getCreatedEpochDateBank()); dto.setCreatedEpochDate(transactionMaster.getCreatedEpochDate());
dto.setModifiedEpochDate(transactionMaster.getModifiedEpochDate()); dto.setModifiedEpochDate(transactionMaster.getModifiedEpochDate());
return dto; return dto;
} }
// Convert TransactionMaster to particular details DTO (used with method // Convert TransactionMaster to particular details DTO (used with method references)
// references)
public TransactionHistoryParticularDetailsDTO billerRemainingDetails(TransactionMaster transactionMaster) { public TransactionHistoryParticularDetailsDTO billerRemainingDetails(TransactionMaster transactionMaster) {
if (transactionMaster == null) if (transactionMaster == null) return null;
return null;
TransactionHistoryParticularDetailsDTO dto = new TransactionHistoryParticularDetailsDTO(); TransactionHistoryParticularDetailsDTO dto = new TransactionHistoryParticularDetailsDTO();
dto.setBankTxnId(transactionMaster.getBankTxnId()); dto.setBankTxnId(transactionMaster.getBankTxnId());
dto.setTxnId(transactionMaster.getTxnId()); dto.setTxnId(transactionMaster.getTxnId());
dto.setCreationDate(transactionMaster.getCreationDateBank()); dto.setCreationDate(transactionMaster.getCreationDate());
dto.setBillerCategory(transactionMaster.getBillerCategory()); dto.setBillerCategory(transactionMaster.getBillerCategory());
dto.setBillerName(transactionMaster.getBillerName()); dto.setBillerName(transactionMaster.getBillerName());
dto.setStatus(transactionMaster.getStatus()); dto.setStatus(transactionMaster.getStatus());
@@ -991,74 +1078,39 @@ public class BillerMapperHelper {
return forward; return forward;
} }
// Map PaymentStartUserRequest + API response into PaymentStartRecord and // Map PaymentStartUserRequest + API response into PaymentStartRecord and persist
// persist public void savePaymentStartRecord(PaymentStartUserRequest request, PaymentStartApiResponse apiResponse,
public PaymentStartRecord savePaymentStartRecord( String txnId, String orderNo, String requestJson, String rawResponse, String responseType) {
PaymentStartUserRequest request, PaymentStartRecord record = new PaymentStartRecord();
PaymentStartApiResponse apiResponse, record.setOrderNo(orderNo);
String txnId, record.setRequestId(request.getReqId());
String orderNo, record.setClientMid(request.getMid());
String requestJson, record.setClientUserId(request.getClientUserId());
String rawResponse, record.setInternalTxnId(txnId);
String responseType) {
PaymentStartRecord record; if (apiResponse != null && apiResponse.getResponseData() != null) {
record.setAmount(Optional.ofNullable(apiResponse.getResponseData().getAmount()).orElse("0.00"));
record.setMid(Optional.ofNullable(apiResponse.getResponseData().getMid()).orElse("DUMMY_MID"));
record.setTxnId(Optional.ofNullable(apiResponse.getResponseData().getTxnId()).orElse("DUMMY_TXN"));
record.setQrString(Optional.ofNullable(apiResponse.getResponseData().getQrString()).orElse("NO_QR"));
// parse createdOn string into LocalDateTime and set on Auditable
LocalDateTime createdOn = parseToLocalDateTime(apiResponse.getResponseData().getCreatedOn());
boolean createNewRecord = record.setTxnStatus("PENDING");
apiResponse == null && } else {
rawResponse == null && record.setAmount("0.00");
responseType == null; record.setMid(request.getMid());
record.setTxnStatus("PENDING");
}
if (createNewRecord) { // store the original request body and raw response
// 🔒 Duplicate check record.setRequestBody(requestJson);
if (paymentStartRecordRepository.findByOrderNo(orderNo).isPresent()) { record.setResponseBody(rawResponse);
throw new CustomException( record.setResponseType(responseType);
"Duplicate OrderNo found",
"DUPLICATE_ORDERNO",
400,
"OrderNo already exists"
);
}
record = new PaymentStartRecord();
} else {
record = paymentStartRecordRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new CustomException(
"OrderNo not found",
"ORDERNO_NOT_FOUND",
404,
"No record exists for given orderNo"
));
}
if (orderNo != null) { paymentStartRecordRepository.save(record);
record.setOrderNo(orderNo);
record.setRequestId(request.getReqId());
record.setMid(request.getMid());
record.setUserId(request.getClientUserId());
record.setTxnId(txnId);
}
if (apiResponse != null && apiResponse.getResponseData() != null) {
record.setAmount(Optional.ofNullable(
apiResponse.getResponseData().getAmount()).orElse("0.00"));
record.setPaymentRefId(Optional.ofNullable(
apiResponse.getResponseData().getTxnId()).orElse("DUMMY_TXN"));
Date createdOn = parseToDate(apiResponse.getResponseData().getCreatedOn());
record.setTxnStatus("PENDING");
} else {
record.setAmount("0.00");
record.setTxnStatus("PENDING");
}
record.setResponseType(responseType);
return paymentStartRecordRepository.save(record);
} }
// Map ComplaintRequestDTO to forward request for complaint API // Map ComplaintRequestDTO to forward request for complaint API
public FetchComplaintForwardRequest fetchComplaintForwardRequestMap(ComplaintRequestDTO complainRequestDTO) { public FetchComplaintForwardRequest fetchComplaintForwardRequestMap(ComplaintRequestDTO complainRequestDTO) {
FetchComplaintForwardRequest forward = new FetchComplaintForwardRequest(); FetchComplaintForwardRequest forward = new FetchComplaintForwardRequest();
@@ -1077,11 +1129,9 @@ public class BillerMapperHelper {
return forward; return forward;
} }
// Check if complaint can be raised (true if within 24 hours of // Check if complaint can be raised (true if within 24 hours of transactionCreatedDateString)
// transactionCreatedDateString)
public static boolean isComplaintAllowed(String transactionCreatedDateString) { public static boolean isComplaintAllowed(String transactionCreatedDateString) {
if (transactionCreatedDateString == null || transactionCreatedDateString.isBlank()) if (transactionCreatedDateString == null || transactionCreatedDateString.isBlank()) return false;
return false;
try { try {
OffsetDateTime transactionCreatedDate = OffsetDateTime.parse(transactionCreatedDateString); OffsetDateTime transactionCreatedDate = OffsetDateTime.parse(transactionCreatedDateString);
OffsetDateTime currentTime = OffsetDateTime.now(); OffsetDateTime currentTime = OffsetDateTime.now();
@@ -1094,33 +1144,9 @@ public class BillerMapperHelper {
} }
} }
// Persist complaint response entity
private String generateComplaintTicketId() {
return "CMP-" + UUID.randomUUID().toString();
}
// Minimal mapping helpers used by AgentServiceImpl
public com.billpaymentservice.bbps.dto.sentRequest.FetchBillForwardRequest fetchBillForwardRequestMap(
com.billpaymentservice.bbps.dto.request.FetchBillRequest req) {
if (req == null)
return null;
com.billpaymentservice.bbps.dto.sentRequest.FetchBillForwardRequest f = new com.billpaymentservice.bbps.dto.sentRequest.FetchBillForwardRequest();
f.setBillerId(req.getBillerId());
f.setBillerName(req.getBillerName());
f.setBillerCategory(req.getBillerCategory());
f.setClientReqId(req.getClientReqId());
f.setMacAdress(req.getMacAdress());
f.setCustomerMobNo(req.getCustomerMobNo());
f.setRemitterName(req.getRemitterName());
f.setPaymentChannel(req.getPaymentChannel());
f.setDeviceBlockTags(req.getDeviceBlockTags());
f.setCustomerParamsRequest(req.getCustomerParamsRequest());
f.setRemitterIdentity(req.getRemitterIdentity());
return f;
}
public String saveComplaintResponseEntity(ComplaintResponse responseWrapper, String requestId, String txnId, public String saveComplaintResponseEntity(ComplaintResponse responseWrapper, String requestId, String txnId,
String userId, String requestTxnId, String mid) { String userId) {
if (responseWrapper == null || responseWrapper.getTicketDetails() == null) { if (responseWrapper == null || responseWrapper.getTicketDetails() == null) {
return "No data"; return "No data";
} }
@@ -1132,9 +1158,7 @@ public class BillerMapperHelper {
complaintResponseEntity.setTicketNpciId(responseWrapper.getTicketDetails().getTicketNpciId()); complaintResponseEntity.setTicketNpciId(responseWrapper.getTicketDetails().getTicketNpciId());
complaintResponseEntity.setTicketDesp(responseWrapper.getTicketDetails().getTicketDesp()); complaintResponseEntity.setTicketDesp(responseWrapper.getTicketDetails().getTicketDesp());
complaintResponseEntity.setTicketDesr(responseWrapper.getTicketDetails().getTicketDesr()); complaintResponseEntity.setTicketDesr(responseWrapper.getTicketDetails().getTicketDesr());
complaintResponseEntity.setRequestTxnId(requestTxnId);
complaintResponseEntity.setMid(mid);
complaintResponseEntity.setCreateDateTime(new Date());
complaintResponseEntity.setMessage(responseWrapper.getMessage()); complaintResponseEntity.setMessage(responseWrapper.getMessage());
complaintResponseEntity.setRemarks(responseWrapper.getRemarks()); complaintResponseEntity.setRemarks(responseWrapper.getRemarks());
complaintResponseEntity.setStatus(responseWrapper.getStatus()); complaintResponseEntity.setStatus(responseWrapper.getStatus());
@@ -1151,10 +1175,33 @@ public class BillerMapperHelper {
} }
return "Failed to save"; return "Failed to save";
} }
private String generateComplaintTicketId() {
return "CMP-" + UUID.randomUUID().toString();
}
// Minimal mapping helpers used by AgentServiceImpl
public com.billpaymentservice.bbps.dto.sentRequest.FetchBillForwardRequest fetchBillForwardRequestMap(
com.billpaymentservice.bbps.dto.request.FetchBillRequest req) {
if (req == null) return null;
com.billpaymentservice.bbps.dto.sentRequest.FetchBillForwardRequest f = new com.billpaymentservice.bbps.dto.sentRequest.FetchBillForwardRequest();
f.setBillerId(req.getBillerId());
f.setBillerName(req.getBillerName());
f.setBillerCategory(req.getBillerCategory());
f.setClientReqId(req.getClientReqId());
f.setMacAdress(req.getMacAdress());
f.setCustomerMobNo(req.getCustomerMobNo());
f.setRemitterName(req.getRemitterName());
f.setPaymentChannel(req.getPaymentChannel());
f.setDeviceBlockTags(req.getDeviceBlockTags());
f.setCustomerParamsRequest(req.getCustomerParamsRequest());
f.setRemitterIdentity(req.getRemitterIdentity());
return f;
}
public com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest fetchBillValidatestForwardRequest( public com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest fetchBillValidatestForwardRequest(
com.billpaymentservice.bbps.dto.request.FetchBillValidatestRequest req) { com.billpaymentservice.bbps.dto.request.FetchBillValidatestRequest req) {
if (req == null) if (req == null) return null;
return null;
com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest f = new com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest(); com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest f = new com.billpaymentservice.bbps.dto.sentRequest.FetchBillValidatestForwardRequest();
f.setBillerId(req.getBillerId()); f.setBillerId(req.getBillerId());
f.setBillerName(req.getBillerName()); f.setBillerName(req.getBillerName());
@@ -1166,11 +1213,10 @@ public class BillerMapperHelper {
return f; return f;
} }
public void saveBillValidation(FetchBillValidateResponse responseWrapper, public void saveBillValidation(com.billpaymentservice.bbps.dto.response.FetchBillValidateResponse responseWrapper,
BillFetchByBillIdRequest billRequest, String txnId,BillValidation entity) { com.billpaymentservice.bbps.dto.request.BillFetchByBillIdRequest billRequest, String txnId) {
if (responseWrapper == null || billRequest == null) if (responseWrapper == null || billRequest == null) return;
return; BillValidation entity = new BillValidation();
entity.setBillId(responseWrapper.getBillId()); entity.setBillId(responseWrapper.getBillId());
entity.setRefId(responseWrapper.getRefId()); entity.setRefId(responseWrapper.getRefId());
entity.setBillerId(responseWrapper.getBillerId()); entity.setBillerId(responseWrapper.getBillerId());
@@ -1184,8 +1230,7 @@ public class BillerMapperHelper {
// Map additionalInfo if present (minimal mapping) // Map additionalInfo if present (minimal mapping)
if (responseWrapper.getAdditionalInfo() != null) { if (responseWrapper.getAdditionalInfo() != null) {
List<AdditionalInfoBillValidate> addList = new ArrayList<>(); List<AdditionalInfoBillValidate> addList = new ArrayList<>();
for (com.billpaymentservice.bbps.dto.AdditionalInfoBillValidateDTO ai : responseWrapper for (com.billpaymentservice.bbps.dto.AdditionalInfoBillValidateDTO ai : responseWrapper.getAdditionalInfo()) {
.getAdditionalInfo()) {
AdditionalInfoBillValidate info = new AdditionalInfoBillValidate(); AdditionalInfoBillValidate info = new AdditionalInfoBillValidate();
// DTO only has name & value // DTO only has name & value
info.setName(ai.getName()); info.setName(ai.getName());
@@ -1217,66 +1262,4 @@ public class BillerMapperHelper {
LOGGER.error("Error saving BillValidation: {}", e.getMessage(), e); LOGGER.error("Error saving BillValidation: {}", e.getMessage(), e);
} }
} }
public ComplaintResponseDto toDto(ComplaintResponseEntity e) {
ComplaintResponseDto dto = new ComplaintResponseDto();
dto.setTicketId(e.getTicketId());
dto.setTicketType(e.getTicketType());
dto.setStatus(e.getStatus());
dto.setUserId(e.getUserId());
dto.setTxnNpciId(e.getTxnNpciId());
dto.setTicketDesr(e.getTicketDesr());
dto.setTicketDesp(e.getTicketDesp());
dto.setComplaintStatusList(e.getComplaintStatusList().stream().map(cs -> {
ComplaintStatusDto d = new ComplaintStatusDto();
d.setNpciTicketStatus(cs.getNpciTicketStatus());
d.setTicketDespo(cs.getTicketDespo());
d.setCreatedTime(cs.getCreatedTime());
d.setTicketId(cs.getTicketId());
d.setNpciTicketStatus(cs.getNpciTicketStatus());
d.setTicketDespo(cs.getTicketDespo());
d.setNpciTicketType(cs.getNpciTicketType());
return d;
}).collect(Collectors.toList()));
dto.setHistoryList(e.getHistoryList().stream().map(h -> {
ComplaintHistoryDTO d = new ComplaintHistoryDTO();
d.setHistoryStatus(h.getHistoryStatus());
d.setTime(h.getTime());
d.setHistoryId(h.getHistoryId());
d.setTicketId(h.getTicketId());
return d;
}).collect(Collectors.toList()));
dto.setStatusDetailsList(e.getStatusDetailsList().stream().map(sd -> {
ComplaintStatusDetailsDto d = new ComplaintStatusDetailsDto();
d.setTicketStatus(sd.getTicketStatus());
d.setTicketType(sd.getTicketType());
d.setTxnAmount(sd.getTxnAmount());
d.setBillerName(sd.getBillerName());
d.setTicketId(sd.getTicketId());
d.setTotalTxnAmount(sd.getTotalTxnAmount());
d.setBillCategory(sd.getBillCategory());
return d;
}).collect(Collectors.toList()));
dto.setViewAllList(e.getViewAllList().stream().map(v -> {
ViewAllComplaintDto d = new ViewAllComplaintDto();
d.setTicketStatus(v.getTicketStatus());
d.setTxnAmount(v.getTxnAmount());
d.setBillerName(v.getBillerName());
d.setTicketId(v.getTicketId());
d.setTxnNpciId(v.getTxnNpciId());
d.setTicketType(v.getTicketType());
d.setBillCategory(v.getBillCategory());
d.setCreatedTime(v.getCreatedTime());
d.setTotalTxnAmount(v.getTotalTxnAmount());
return d;
}).collect(Collectors.toList()));
return dto;
}
} }
@@ -0,0 +1,20 @@
package com.billpaymentservice.bbps.util;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class ConfigHelper {
}
@@ -29,6 +29,7 @@ public class EncryptionUtil {
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES"); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedData = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); byte[] encryptedData = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
@@ -14,13 +14,10 @@ import org.springframework.stereotype.Component;
import com.billpaymentservice.bbps.dto.UserDetailsDTO; import com.billpaymentservice.bbps.dto.UserDetailsDTO;
import com.billpaymentservice.bbps.dto.auth.RegisterRequest; import com.billpaymentservice.bbps.dto.auth.RegisterRequest;
import com.billpaymentservice.bbps.dto.response.BillDataResponse;
import com.billpaymentservice.bbps.entites.BBPSMastersData; import com.billpaymentservice.bbps.entites.BBPSMastersData;
import com.billpaymentservice.bbps.entites.BharatConnectMaster; import com.billpaymentservice.bbps.entites.BharatConnectMaster;
import com.billpaymentservice.bbps.entites.BillMaster;
import com.billpaymentservice.bbps.entites.InboundTraceLog; import com.billpaymentservice.bbps.entites.InboundTraceLog;
import com.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog;
import com.billpaymentservice.bbps.entites.TransactionMaster;
import com.billpaymentservice.bbps.entites.UserEntity; import com.billpaymentservice.bbps.entites.UserEntity;
import com.billpaymentservice.bbps.exception.CustomException; import com.billpaymentservice.bbps.exception.CustomException;
import com.billpaymentservice.bbps.repository.BBPSMasterDataRepo; import com.billpaymentservice.bbps.repository.BBPSMasterDataRepo;
@@ -33,7 +30,7 @@ import com.billpaymentservice.bbps.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@Component @Component
public class Helper { public class Helper extends ConfigHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(Helper.class); private static final Logger LOGGER = LoggerFactory.getLogger(Helper.class);
private final InboundTraceLogRepository inboundTraceLogRepository; private final InboundTraceLogRepository inboundTraceLogRepository;
@@ -228,68 +225,5 @@ public class Helper {
return userId; return userId;
} }
public void mergeNonNullFields(TransactionMaster src, TransactionMaster dest) {
/*
* LOGGER.info("TransactionMaster src: {}", src);
*/ // Only copy values that are non-null (and non-blank for Strings)
if (src.getBankTxnId() != null && !src.getBankTxnId().isBlank()) dest.setBankTxnId(src.getBankTxnId());
if (src.getTxnId() != null && !src.getTxnId().isBlank()) dest.setTxnId(src.getTxnId());
if (src.getInitiatorTxnId() != null && !src.getInitiatorTxnId().isBlank()) dest.setInitiatorTxnId(src.getInitiatorTxnId());
if (src.getCheckerTxnId() != null && !src.getCheckerTxnId().isBlank()) dest.setCheckerTxnId(src.getCheckerTxnId());
if (src.getReqId() != null && !src.getReqId().isBlank()) dest.setReqId(src.getReqId());
if (src.getMid() != null && !src.getMid().isBlank()) dest.setMid(src.getMid());
if (src.getBankUserId()!= null && !src.getBankUserId().isBlank()) dest.setBankUserId(src.getBankUserId());
if (src.getClientReqId() != null && !src.getClientReqId().isBlank()) dest.setClientReqId(src.getClientReqId());
if (src.getUserId() != null && !src.getUserId().isBlank()) dest.setUserId(src.getUserId());
if (src.getBillerId() != null && !src.getBillerId().isBlank()) dest.setBillerId(src.getBillerId());
if (src.getTxnAmount() != null) dest.setTxnAmount(src.getTxnAmount());
if (src.getStatus() != null && !src.getStatus().isBlank()) dest.setStatus(src.getStatus());
if (src.getPaymentInformation() != null && !src.getPaymentInformation().isBlank()) dest.setPaymentInformation(src.getPaymentInformation());
if (src.getCreationDateBank() != null) dest.setCreationDateBank(src.getCreationDateBank());
if (src.getModifiedDateBank() != null) dest.setModifiedDateBank(src.getModifiedDateBank());
if (src.getCreatedEpochDateBank() != null) dest.setCreatedEpochDateBank(src.getCreatedEpochDateBank());
if (src.getModifiedEpochDateBank() != null) dest.setModifiedEpochDateBank(src.getModifiedEpochDateBank());
/* ---- additional fields ---- */
if (src.getRefId() != null && !src.getRefId().isBlank()) dest.setRefId(src.getRefId());
if (src.getPaymentRefId() != null && !src.getPaymentRefId().isBlank()) dest.setPaymentRefId(src.getPaymentRefId());
if (src.getRemitterName() != null && !src.getRemitterName().isBlank()) dest.setRemitterName(src.getRemitterName());
if (src.getOrderId() != null && !src.getOrderId().isBlank()) dest.setOrderId(src.getOrderId());
if (src.getBillId() != null && !src.getBillId().isBlank()) dest.setBillId(src.getBillId());
if (src.getUserType() != null && !src.getUserType().isBlank()) dest.setUserType(src.getUserType());
if (src.getTotalTxnAmount() != null) dest.setTotalTxnAmount(src.getTotalTxnAmount());
if (src.getBillerCategory() != null && !src.getBillerCategory().isBlank()) dest.setBillerCategory(src.getBillerCategory());
if (src.getBillerName() != null && !src.getBillerName().isBlank()) dest.setBillerName(src.getBillerName());
if (src.getOffUsPay() != null) dest.setOffUsPay(src.getOffUsPay());
if (src.getSplitPay() != null) dest.setSplitPay(src.getSplitPay());
if (src.getQuickPay() != null) dest.setQuickPay(src.getQuickPay());
if (src.getPaymentMode() != null && !src.getPaymentMode().isBlank()) dest.setPaymentMode(src.getPaymentMode());
if (src.getAgentPaymentInformation() != null && !src.getAgentPaymentInformation().isBlank()) dest.setAgentPaymentInformation(src.getAgentPaymentInformation());
if (src.getCustConvFee() != null) dest.setCustConvFee(src.getCustConvFee());
if (src.getCurrency() != null && !src.getCurrency().isBlank()) dest.setCurrency(src.getCurrency());
if (src.getRequestedIp() != null && !src.getRequestedIp().isBlank()) dest.setRequestedIp(src.getRequestedIp());
if (src.getCustomerMobNo() != null && !src.getCustomerMobNo().isBlank()) dest.setCustomerMobNo(src.getCustomerMobNo());
if (src.getMacAddress() != null && !src.getMacAddress().isBlank()) dest.setMacAddress(src.getMacAddress());
if (src.getSplitPayAmount() != null) dest.setSplitPayAmount(src.getSplitPayAmount());
if (src.getCouCustConvFee() != null) dest.setCouCustConvFee(src.getCouCustConvFee());
if (src.getSettlementVerification() != null) dest.setSettlementVerification(src.getSettlementVerification());
if (src.getFundTxnType() != null && !src.getFundTxnType().isBlank()) dest.setFundTxnType(src.getFundTxnType());
if (src.getReason() != null && !src.getReason().isBlank()) dest.setReason(src.getReason());
if (src.getAmountTags() != null && !src.getAmountTags().isBlank()) dest.setAmountTags(src.getAmountTags());
if (src.getPlanId() != null && !src.getPlanId().isBlank()) dest.setPlanId(src.getPlanId());
if (src.getPlanType() != null && !src.getPlanType().isBlank()) dest.setPlanType(src.getPlanType());
if (src.getTxnNpciId() != null && !src.getTxnNpciId().isBlank()) dest.setTxnNpciId(src.getTxnNpciId());
/* ---- lists (direct replace when provided) ---- */
if (src.getDeviceBlockTags() != null && !src.getDeviceBlockTags().isEmpty()) dest.setDeviceBlockTags(src.getDeviceBlockTags());
if (src.getCustomerParams() != null && !src.getCustomerParams().isEmpty()) dest.setCustomerParams(src.getCustomerParams());
LOGGER.info("TransactionMaster dest after merge: {}", dest);
}
public void mergeNonNullFieldsBillIntiate(BillDataResponse responseWrapper, BillMaster existingbill) {
}
} }
@@ -21,7 +21,7 @@ swagger.server.description=Local Development Server
#spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_new_updated #spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_new_updated
spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_enhanced spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_enhanced
>>>>>>> Stashed changes
spring.datasource.username=postgres spring.datasource.username=postgres
#spring.datasource.password=UAT@DB_2025 #spring.datasource.password=UAT@DB_2025
@@ -55,9 +55,6 @@ master.data.url = https://bbps-staging.digiledge.in/agent/cou-master/masters
agent.data.url=https://bbps-staging.digiledge.in/agent/bill-payment-new agent.data.url=https://bbps-staging.digiledge.in/agent/bill-payment-new
payment.data.url=https://bbps-staging.digiledge.in/couapp payment.data.url=https://bbps-staging.digiledge.in/couapp
resilience4j.ratelimiter.configs.default.limit-for-period=1000
resilience4j.ratelimiter.configs.default.limit-refresh-period=1m
resilience4j.ratelimiter.configs.default.timeout-duration=0
# Resilience4j Retry # Resilience4j Retry
resilience4j.retry.instances.callbackService.max-attempts=1 resilience4j.retry.instances.callbackService.max-attempts=1
+1 -7
View File
@@ -21,7 +21,7 @@ spring.profiles.active=local
# DataSource # DataSource
spring.datasource.url=jdbc:postgresql://localhost:5432/devdbuat spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_new_updated
spring.datasource.username=postgres spring.datasource.username=postgres
#spring.datasource.password=UAT@DB_2025 #spring.datasource.password=UAT@DB_2025
@@ -72,13 +72,7 @@ spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
jwt.secret=6C756148487475787A414646736D4B6E444E585A766145396E6A4555594A595566657A6E434D62536856674A704C494F6C6E666B366A4A6A6D5630323564716C614F jwt.secret=6C756148487475787A414646736D4B6E444E585A766145396E6A4555594A595566657A6E434D62536856674A704C494F6C6E666B366A4A6A6D5630323564716C614F
# ===============================
# Resilience4j Rate Limiter
# ===============================
resilience4j.ratelimiter.configs.default.limit-for-period=1000
resilience4j.ratelimiter.configs.default.limit-refresh-period=1m
resilience4j.ratelimiter.configs.default.timeout-duration=0
# Resilience4j Retry # Resilience4j Retry
resilience4j.retry.instances.callbackService.max-attempts=1 resilience4j.retry.instances.callbackService.max-attempts=1
resilience4j.retry.instances.callbackService.wait-duration=2s resilience4j.retry.instances.callbackService.wait-duration=2s
@@ -0,0 +1,189 @@
package com.billpaymentservice.bbps;
import java.net.URI;
import java.net.http.*;
import java.util.*;
import java.util.concurrent.*;
import org.json.*;
public class BBPSAutomationTest {
private static final String BASE_URL = "http://localhost:9090/masterData";
private static final String BILLER_API = "https://bbps-staging.digiledge.in/agent/cou-master/masters/billers";
private static final HttpClient client = HttpClient.newHttpClient();
// Run 10 threads in parallel
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
try {
System.out.println("🚀 Starting BBPS Test Flow (Multi-Threaded)...\n");
List<String> categories = getAllCategories();
if (categories.isEmpty()) {
System.out.println("❌ No categories found. Exiting...");
return;
}
System.out.println("\n✅ Total Categories Found: " + categories.size());
int count = 1;
for (String category : categories) {
System.out.println("\n📂 [" + count++ + "/" + categories.size() + "] Category: " + category);
List<String> billerIds = Collections.emptyList();
try {
billerIds = fetchBillersByCategory(category);
} catch (Exception ex) {
System.err.println("⚠️ Skipping category '" + category + "' due to error: " + ex.getMessage());
continue; // skip and go to next category
}
if (billerIds == null || billerIds.isEmpty()) {
System.out.println("⚠️ No billers found for: " + category + " — Skipping...");
continue;
}
System.out.println("🔹 Found " + billerIds.size() + " billers. Fetching details in parallel...");
// Run all biller calls in parallel threads
List<Future<?>> futures = new ArrayList<>();
for (String billerId : billerIds) {
futures.add(executor.submit(() -> {
try {
sendBillerDetails(billerId);
} catch (Exception e) {
System.err.println("❌ Error with biller " + billerId + ": " + e.getMessage());
}
}));
}
// Wait for all billers to finish
for (Future<?> f : futures) {
try {
f.get();
} catch (Exception e) {
System.err.println("⚠️ Thread interrupted: " + e.getMessage());
}
}
System.out.println("✅ Completed all billers for category: " + category);
}
executor.shutdown();
System.out.println("\n🎯 All categories processed successfully (multi-threaded & safe)!");
} catch (Exception e) {
e.printStackTrace();
}
}
// Step 1 - Get All Categories
private static List<String> getAllCategories() throws Exception {
String reqId = UUID.randomUUID().toString();
JSONObject body = new JSONObject();
body.put("mid", "ALLCOS00117BHO");
body.put("reqId", reqId);
body.put("secretKey", "ABCD1234SECRET");
JSONObject device = new JSONObject();
device.put("deviceId", "DEVICE001");
device.put("deviceName", "Samsung Galaxy S23");
device.put("os", "Android 14");
device.put("appVersion", "1.0.5");
device.put("ipAddress", "192.168.1.101");
body.put("deviceDetial", device);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/getAllMastersData"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.out.println("❌ getAllMastersData failed | HTTP " + response.statusCode());
return Collections.emptyList();
}
JSONObject json = new JSONObject(response.body());
JSONArray catArray = json.optJSONObject("data") != null
? json.getJSONObject("data").optJSONArray("categoryRespList")
: null;
List<String> categories = new ArrayList<>();
if (catArray != null && catArray.length() > 0) {
for (int i = 0; i < catArray.length(); i++) {
categories.add(catArray.getJSONObject(i).getString("categoryName"));
}
}
System.out.println("✅ Categories found: " + categories.size());
return categories;
}
// Step 2 - Fetch Billers by Category
private static List<String> fetchBillersByCategory(String category) throws Exception {
// Encode category to handle spaces & special characters safely
String encodedCategory = java.net.URLEncoder.encode(category, java.nio.charset.StandardCharsets.UTF_8);
String url = BILLER_API + "?category=" + encodedCategory + "&page=0&pagesize=50";
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.out.println("❌ Failed to fetch billers for " + category + " | HTTP " + response.statusCode());
return Collections.emptyList();
}
JSONObject json = new JSONObject(response.body());
JSONArray billersArray = json.optJSONArray("billerResp");
if (billersArray == null || billersArray.length() == 0) {
System.out.println("⚠️ billerResp empty or null for category: " + category);
return Collections.emptyList();
}
List<String> billerIds = new ArrayList<>();
for (int i = 0; i < billersArray.length(); i++) {
billerIds.add(billersArray.getJSONObject(i).getString("billerId"));
}
return billerIds;
}
// Step 3 - Get Biller Details
private static void sendBillerDetails(String billerId) throws Exception {
String reqId = UUID.randomUUID().toString();
JSONObject body = new JSONObject();
body.put("mid", "ALLCOS00117BHO");
body.put("reqId", reqId);
body.put("billerId", billerId);
body.put("secretKey", "ABCD1234SECRET");
JSONObject device = new JSONObject();
device.put("deviceId", "DEVICE001");
device.put("deviceName", "Samsung Galaxy S23");
device.put("os", "Android 14");
device.put("appVersion", "1.0.5");
device.put("ipAddress", "192.168.1.101");
body.put("deviceDetial", device);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/getBillerDetails"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
synchronized (System.out) { // thread-safe log printing
if (response.statusCode() == 200) {
System.out.println("✅ Success: " + billerId);
} else {
System.out.println("❌ Failed: " + billerId + " | HTTP " + response.statusCode());
}
}
}
}
@@ -0,0 +1,109 @@
package com.billpaymentservice.bbps.filter;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.Collections;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import com.billpaymentservice.bbps.ratelimit.LocalBucketManager;
import io.github.bucket4j.Bucket;
class RateLimitingFilterTest {
private StringRedisTemplate redisTemplate;
private LocalBucketManager localBucketManager;
private RateLimitingFilter filter;
@BeforeEach
void setup() {
redisTemplate = mock(StringRedisTemplate.class);
localBucketManager = mock(LocalBucketManager.class);
filter = new RateLimitingFilter(redisTemplate, localBucketManager);
}
@Test
void whenLocalBucketExhausted_thenReturns429() throws ServletException, IOException {
MockHttpServletRequest req = new MockHttpServletRequest();
req.addHeader("mid", "MID_LOCAL_EXHAUST");
MockHttpServletResponse resp = new MockHttpServletResponse();
Bucket mockBucket = mock(Bucket.class);
when(localBucketManager.getBucket("local:mid:MID_LOCAL_EXHAUST")).thenReturn(mockBucket);
when(mockBucket.tryConsume(1)).thenReturn(false);
FilterChain chain = mock(FilterChain.class);
filter.doFilter(req, resp, chain);
assertEquals(429, resp.getStatus());
String content = resp.getContentAsString();
assertTrue(content.contains("Rate limit exceeded (local)"));
verify(chain, never()).doFilter(any(), any());
}
@Test
void whenLocalAllowsAndRedisUnderLimit_thenPassesThrough() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.addHeader("mid", "MID_OK");
MockHttpServletResponse resp = new MockHttpServletResponse();
Bucket mockBucket = mock(Bucket.class);
when(localBucketManager.getBucket("local:mid:MID_OK")).thenReturn(mockBucket);
when(mockBucket.tryConsume(1)).thenReturn(true);
when(mockBucket.getAvailableTokens()).thenReturn(1000L);
// stub redis execute to return 1 (below limit)
when(redisTemplate.execute(any(DefaultRedisScript.class), anyList(), eq("2"))).thenReturn(1L);
// use a FilterChain that sets a flag by setting response status to 200
FilterChain chain = (request, response) -> {
((MockHttpServletResponse) response).setStatus(200);
};
filter.doFilter(req, resp, chain);
assertEquals(200, resp.getStatus());
// headers should be present
assertEquals("1200", resp.getHeader("X-RateLimit-Limit"));
assertNotNull(resp.getHeader("X-RateLimit-Remaining"));
}
@Test
void whenLocalAllowsButRedisOverLimit_thenReturns429AndReturnsToken() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.addHeader("mid", "MID_GLOBAL_EXHAUST");
MockHttpServletResponse resp = new MockHttpServletResponse();
Bucket mockBucket = mock(Bucket.class);
when(localBucketManager.getBucket("local:mid:MID_GLOBAL_EXHAUST")).thenReturn(mockBucket);
when(mockBucket.tryConsume(1)).thenReturn(true);
when(mockBucket.getAvailableTokens()).thenReturn(0L);
// stub redis execute to indicate global over-limit
when(redisTemplate.execute(any(DefaultRedisScript.class), anyList(), eq("2"))).thenReturn(1201L);
FilterChain chain = mock(FilterChain.class);
filter.doFilter(req, resp, chain);
assertEquals(429, resp.getStatus());
String content = resp.getContentAsString();
assertTrue(content.contains("Rate limit exceeded (global)"));
// verify token returned to local bucket
verify(mockBucket, times(1)).addTokens(1);
verify(chain, never()).doFilter(any(), any());
}
}