Author SHA1 Message Date
Lincpay Tech 38979f0f94 transaction master and payment start record new entity with update 2025-12-23 10:38:59 +05:30
Lincpay Tech 46e50acc88 changed client to mid only 2025-12-22 18:29:24 +05:30
himanshu 95e1c8b45d Merge pull request 'feature/resilience4j' (#43) from feature/resilience4j into develop
Reviewed-on: https://git.zeptic.in/himanshu/bbps/pulls/43
2025-12-22 18:13:18 +05:30
Lincpay Tech 6126bc1d81 resolved the conflicts 2025-12-22 18:10:12 +05:30
Lincpay Tech 5d0f043848 improved bill fetching data is getting saved before making call 2025-12-22 17:12:54 +05:30
Lincpay Tech a4842788c1 removed and added lombook for the login,verify,payment,transaction 2025-12-22 10:32:29 +05:30
Mohd_Arham a9d04ee757 Merge pull request 'feature3-userId-added' (#39) from feature3-userId-added into develop
Reviewed-on: https://git.zeptic.in/himanshu/bbps/pulls/39
2025-12-19 12:21:33 +05:30
Mohd-Arham e3fdca6f72 updated code mergin 19-12-25 2025-12-19 12:20:27 +05:30
Mohd-Arham 0197f6951f updated code 2025-12-19 12:15:32 +05:30
Mohd_Arham 7fac2cb590 Merge pull request 'feature/id-length-fix' (#38) from feature/id-length-fix into develop
Reviewed-on: https://git.zeptic.in/himanshu/bbps/pulls/38
2025-12-19 12:00:26 +05:30
Lincpay Tech 596f519d1e ready to go code and improved 2025-12-19 11:48:17 +05:30
Lincpay Tech 705af33f1e merge with develop in local now ready to merge in origin 2025-12-18 23:45:47 +05:30
Lincpay Tech ae298a35f6 delete some colums from payment start record 2025-12-18 23:24:38 +05:30
Lincpay Tech f752ed269f improved the logging and corrected payement start record 2025-12-18 21:57:56 +05:30
Lincpay Tech d34b2e1b84 changed trasaction master saving 2025-12-18 10:20:49 +05:30
Lincpay Tech c8bc6e7f12 half commited 2025-12-17 15:30:33 +05:30
Mohd_Arham 67be2acbc4 Merge pull request 'feature3-userId-added' (#36) from feature3-userId-added into develop
Reviewed-on: https://git.zeptic.in/himanshu/bbps/pulls/36
2025-12-17 10:59:48 +05:30
Mohd-Arham d3ae7decc8 updated code 2025-12-17 10:58:26 +05:30
Mohd-Arham a41dca5693 code added on updated 2025-12-17 10:56:34 +05:30
Mohd_Arham 499bb1e995 Merge pull request 'feature/Resilience4j' (#35) from feature/Resilience4j into develop
Reviewed-on: https://git.zeptic.in/himanshu/bbps/pulls/35
2025-12-17 10:55:40 +05:30
Lincpay Tech 4b5f81d0d5 added rate limiter and improved payment start entity 2025-12-17 10:49:39 +05:30
Lincpay Tech 0e747ed2a7 updated entity and removed mid from paymentstartRecord 2025-12-16 16:50:48 +05:30
Lincpay Tech f9ba70414b improved json failed response 2025-12-16 10:58:04 +05:30
Mohd_Arham d133fc0c75 Merge pull request 'feature3-userId-added' (#34) from feature3-userId-added into develop
Reviewed-on: https://git.zeptic.in/himanshu/bbps/pulls/34
2025-12-16 09:59:54 +05:30
Mohd-Arham a2f9ee8ed7 added code 2025-12-16 09:59:05 +05:30
Mohd-Arham 98ca620bb4 added code 2025-12-16 09:57:29 +05:30
Lincpay Tech 8ae11f86d4 Merge branch 'develop' of https://git.zeptic.in/himanshu/bbps into develop 2025-12-15 12:26:35 +05:30
Lincpay Tech eeb326a221 improved the error handling and updated link paymentstartrecord and transactionmaster 2025-12-15 12:25:40 +05:30
Mohd_Arham efd6a58b62 Merge pull request 'feature3-userId-added' (#33) from feature3-userId-added into develop
Reviewed-on: https://git.zeptic.in/himanshu/bbps/pulls/33
2025-12-12 15:11:02 +05:30
Mohd-Arham 78c0e023d9 update code 2025-12-12 15:09:43 +05:30
Mohd-Arham 28cf0aa98d add requestId in complaint 2025-12-12 15:02:05 +05:30
55 changed files with 1109 additions and 1459 deletions
+9 -14
View File
@@ -44,6 +44,12 @@
</exclusion> </exclusion>
</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>
@@ -179,19 +185,8 @@
<artifactId>spring-boot-configuration-processor</artifactId> <artifactId>spring-boot-configuration-processor</artifactId>
<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>
@@ -221,4 +216,4 @@
<finalName>BBPS</finalName> <finalName>BBPS</finalName>
</build> </build>
</project> </project>
@@ -1,68 +0,0 @@
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") .requestMatchers("/payment/callback/startPayment","/complaints/checkTicketStatus")
.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,7 +46,6 @@ 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);
} }
@@ -0,0 +1,24 @@
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;
}
@@ -0,0 +1,18 @@
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;
}
@@ -0,0 +1,17 @@
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,9 +66,12 @@ public class TransactionDTO {
private String planType; private String planType;
// ✅ Date fields as String (for JSON compatibility) // ✅ Date fields as String (for JSON compatibility)
private String creationDate; @JsonProperty("creationDate")
private String modifiedDate; private String creationDateBank;
@JsonProperty("modifiedDate")
private Long createdEpochDate; private String modifiedDateBank;
private Long modifiedEpochDate; @JsonProperty("createdEpochDate")
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 Double txnAmount; private String txnAmount;
private Double totalTxnAmount; private String 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 Double custConvFee; private String 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 Double splitPayAmount; private String splitPayAmount;
private Double couCustConvFee; private String couCustConvFee;
private Boolean settlementVerification; private String 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 Double totalTxnAmount; private String totalTxnAmount;
private String bankTxnId; private String bankTxnId;
@@ -0,0 +1,20 @@
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,7 +3,16 @@ 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.")
@@ -11,18 +20,5 @@ 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,29 +1,17 @@
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,11 +6,15 @@ 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,7 +3,15 @@ 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.")
@@ -16,27 +24,5 @@ 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;
}
} }
@@ -0,0 +1,17 @@
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,29 +6,37 @@ 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) {
LocalDateTime now = LocalDateTime.now(); Date now = new Date(); // current date + time
// Creation fields // creation fields
setIfNull(entity, "createDateTime", now); setIfNull(entity, "createDateTime", now);
setIfNull(entity, "createdBy", null); // handle manually in service if needed setIfNull(entity, "createdBy", null);
setIfNull(entity, "createdEpochDate", System.currentTimeMillis()); setIfNull(entity, "createdEpochDate", now.getTime());
// Set isDeleted = false on creation // default soft delete flag
setAlways(entity, "isDeleted", Boolean.FALSE); setAlways(entity, "isDeleted", Boolean.FALSE);
} }
/* @PreUpdate
* @PreUpdate public void preUpdate(Object entity) { LocalDateTime now = public void preUpdate(Object entity) {
* LocalDateTime.now(); Date now = new Date();
*
* // Update fields setAlways(entity, "updatedDateTime", now); setIfNull(entity, // update fields
* "updatedBy", null); // you can override manually in service setAlways(entity, setAlways(entity, "updatedDateTime", now);
* "modifiedEpochDate", System.currentTimeMillis()); } setIfNull(entity, "updatedBy", null);
*/ 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);
@@ -37,8 +45,7 @@ public class AuditListener {
boolean accessible = f.canAccess(entity); boolean accessible = f.canAccess(entity);
f.setAccessible(true); f.setAccessible(true);
Object currentValue = f.get(entity); if (f.get(entity) == null) {
if (currentValue == null) {
f.set(entity, value); f.set(entity, value);
} }
@@ -53,9 +60,7 @@ 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,6 +1,7 @@
package com.billpaymentservice.bbps.entites; package com.billpaymentservice.bbps.entites;
import java.time.LocalDateTime; import java.io.Serializable;
import java.util.Date;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners; import jakarta.persistence.EntityListeners;
@@ -9,33 +10,34 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
/** /**
* Base mapped superclass for audit fields. All JPA entities should extend this to * Base mapped superclass for audit fields. All JPA entities should extend this
* get automatic population of createdAt/updatedAt/isDeleted via AuditListener. * to get automatic population of createdAt/updatedAt/isDeleted via
* AuditListener.
*/ */
@MappedSuperclass @MappedSuperclass
@EntityListeners(AuditListener.class) @EntityListeners(AuditListener.class)
@Getter @Getter
@Setter @Setter
public abstract class Auditable { public abstract class Auditable implements Serializable {
@Column(name = "create_date_time") @Column(name = "create_date_time")
private LocalDateTime createDateTime; private Date createDateTime;
@Column(name = "updated_date_time") @Column(name = "updated_date_time")
private LocalDateTime updatedDateTime; private Date updatedDateTime;
@Column(name = "create_by") @Column(name = "create_by")
private LocalDateTime createdBy; private String createdBy;
@Column(name = "updated_by") @Column(name = "updated_by")
private LocalDateTime updatedBy; private String 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")
private Boolean isDeleted = Boolean.FALSE;
@Column(name = "is_deleted")
private Boolean isDeleted = Boolean.FALSE;
} }
@@ -5,6 +5,8 @@ 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;
@@ -26,16 +28,24 @@ 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;
@@ -43,6 +53,4 @@ 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,15 +8,24 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@Entity @Entity
@Table(name = "bbps_bill_validation") @Table(name = "bbps_bill_validation_master")
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class BillValidation extends Auditable { public class BillValidation extends Auditable {
@Id @Id
@Column(name = "bill_id", nullable = false, length = 100) @GeneratedValue(strategy = GenerationType.IDENTITY)
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;
@@ -55,6 +64,4 @@ 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,6 +2,7 @@ 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;
@@ -71,9 +72,4 @@ 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,6 +2,7 @@ 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;
@@ -46,8 +47,5 @@ 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,14 +1,16 @@
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;
@@ -30,6 +32,9 @@ 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;
@@ -72,17 +77,23 @@ public class ComplaintResponseEntity extends Auditable {
@Column(name = "user_id") @Column(name = "user_id")
private String userId; private String userId;
@OneToMany(mappedBy = "complaintResponse", cascade = CascadeType.ALL, orphanRemoval = false) @Column(name = "mid")
private List<ComplainStatus> complaintStatusList; private String mid;
@OneToMany(mappedBy = "complaintResponse", cascade = CascadeType.ALL, orphanRemoval = false) @OneToMany(fetch = FetchType.LAZY)
private List<ComplaintHistory> historyList; @JoinColumn(name = "ticket_id", referencedColumnName = "ticket_id", insertable = false, updatable = false)
private Set<ComplainStatus> complaintStatusList = new HashSet<>();
@OneToMany(mappedBy = "complaintResponse", cascade = CascadeType.ALL, orphanRemoval = false) @OneToMany(fetch = FetchType.LAZY)
private List<ComplaintStatusDetails> statusDetailsList; @JoinColumn(name = "ticket_id", referencedColumnName = "ticket_id", insertable = false, updatable = false)
private Set<ComplaintHistory> historyList = new HashSet<>();
@OneToMany(mappedBy = "complaintResponse", cascade = CascadeType.ALL, orphanRemoval = false) @OneToMany(fetch = FetchType.LAZY)
private List<ViewAllComplaintTicketStatusDetails> viewAllList; @JoinColumn(name = "ticket_id", referencedColumnName = "ticket_id", insertable = false, updatable = false)
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,6 +2,7 @@ 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;
@@ -88,8 +89,6 @@ 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,9 +1,5 @@
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;
@@ -11,30 +7,15 @@ import lombok.NoArgsConstructor;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Entity public class PaymentInitiate {
@Table(name = "bbps_payment_initiate")
public class PaymentInitiate extends Auditable { // Kept as a POJO for compatibility but not annotated as @Entity so JPA won't map it
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,6 +11,11 @@ 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
@@ -21,20 +26,20 @@ import lombok.Setter;
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 = "client_mid", nullable = false) @Column(name = "mid", nullable = false)
private String clientMid; private String mid;
@Column(name = "client_user_id") @Column(name = "user_id")
private String clientUserId; private String userId;
@Column(name = "internal_txn_id", unique = true, nullable = false) @Column(name = "txn_id", unique = true, nullable = false)
private String internalTxnId; private String txnId;
@Column(name = "order_no", unique = true, nullable = false) @Column(name = "order_no", unique = true, nullable = false)
private String orderNo; private String orderNo;
@@ -42,42 +47,28 @@ public class PaymentStartRecord extends Auditable {
@Column(name = "amount", nullable = false) @Column(name = "amount", nullable = false)
private String amount; private String amount;
@Column(name = "mid", nullable = false) @Column(name = "payment_ref_id",unique = true)
private String mid; private String paymentRefId;
@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; // e.g., "QR", "HTML", "JSON" private String responseType;
@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,13 +2,18 @@ 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;
@@ -25,19 +30,27 @@ import lombok.ToString;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class TransactionMaster extends Auditable { public class TransactionMaster extends Auditable {
// 👇 JSON me "txn_id" aayega, ye DB me bank_txn_id column me save hoga @Id
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@JsonProperty("txn_id") @JsonProperty("txn_id")
@Column(name = "bank_txn_id", nullable = false, unique = true) @Column(name = "bank_txn_id",unique = true)
private String bankTxnId; private String bankTxnId;
// 👇 Apna internal transaction id, manually generate or system ke through @Column(name = "txn_id",unique = true)
@Column(name = "txn_id")
private String txnId; private String txnId;
@Column(name = "initiator_txn_id")
private String initiatorTxnId;
@Column(name = "checker_txn_id")
private String checkerTxnId;
@Column(name = "client_user_id") @Column(name = "user_id")
private String clientUserId; private String userId;
@Column(name = "mid") @Column(name = "mid")
private String mid; private String mid;
@@ -66,14 +79,14 @@ public class TransactionMaster extends Auditable {
@Column(name = "user_type") @Column(name = "user_type")
private String userType; private String userType;
@Column(name = "user_id") @Column(name = "bank_user_id")
private String userId; private String bankUserId;
@Column(name = "txn_amount") @Column(name = "txn_amount")
private Double txnAmount; private String txnAmount;
@Column(name = "total_txn_amount") @Column(name = "total_txn_amount")
private Double totalTxnAmount; private String totalTxnAmount;
@Column(name = "biller_id") @Column(name = "biller_id")
private String billerId; private String billerId;
@@ -103,7 +116,7 @@ public class TransactionMaster extends Auditable {
private String agentPaymentInformation; private String agentPaymentInformation;
@Column(name = "cust_conv_fee") @Column(name = "cust_conv_fee")
private Double custConvFee; private String custConvFee;
@Column(name = "currency") @Column(name = "currency")
private String currency; private String currency;
@@ -121,13 +134,13 @@ public class TransactionMaster extends Auditable {
private String macAddress; private String macAddress;
@Column(name = "split_pay_amount") @Column(name = "split_pay_amount")
private Double splitPayAmount; private String splitPayAmount;
@Column(name = "cou_cust_conv_fee") @Column(name = "cou_cust_conv_fee")
private Double couCustConvFee; private String couCustConvFee;
@Column(name = "settlement_verification") @Column(name = "settlement_verification")
private Boolean settlementVerification; private String settlementVerification;
@Column(name = "fund_txn_type") @Column(name = "fund_txn_type")
private String fundTxnType; private String fundTxnType;
@@ -149,21 +162,27 @@ 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") @Column(name = "creation_date_bank")
private String creationDate; private String creationDateBank;
@Column(name = "modified_date") @Column(name = "modified_date_bank")
private String modifiedDate; private String modifiedDateBank;
@Column(name = "created_epoch_date") @Column(name = "created_epoch_date_bank")
private Long createdEpochDate; private Long createdEpochDateBank;
@Column(name = "modified_epoch_date") @Column(name = "modified_epoch_date_bank")
private Long modifiedEpochDate; private Long modifiedEpochDateBank;
} }
@@ -2,6 +2,7 @@ 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;
@@ -88,7 +89,4 @@ 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;
} }
@@ -1,174 +0,0 @@
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);
}
}
@@ -1,34 +0,0 @@
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,6 +4,17 @@ import org.springframework.data.jpa.repository.JpaRepository;
import com.billpaymentservice.bbps.entites.BillMaster; import com.billpaymentservice.bbps.entites.BillMaster;
public interface BillMasterRepository extends JpaRepository<BillMaster, String> { import java.util.Optional;
} /**
* 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,6 +6,8 @@ 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, String> { public interface BillValidationRepository extends JpaRepository<BillValidation, Long> {
BillValidation findByInitiatorTxnId(String txnId);
} }
@@ -15,7 +15,17 @@ 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,9 +1,16 @@
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,17 +1,6 @@
package com.billpaymentservice.bbps.repository; package com.billpaymentservice.bbps.repository;
import java.util.Optional; // Legacy stub: PaymentInitiate table removed; keep a placeholder class to avoid compilation errors if referenced.
public class PaymentIntiateRepository {
import org.springframework.data.jpa.repository.JpaRepository; // Intentionally empty - PaymentInitiate is no longer a JPA entity and this repo is retired.
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,6 +1,7 @@
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;
@@ -10,7 +11,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, String> { public interface TransactionMasterRepository extends JpaRepository<TransactionMaster, Long> {
// @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,
@@ -20,11 +21,29 @@ 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.clientUserId = :clientUserId") @Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid AND t.userId = :userId")
Page<TransactionMaster> findByMidOrClientUserId(@Param("mid") String mid, Page<TransactionMaster> findByMidAndUserId(
@Param("clientUserId") String clientUserId, Pageable pageable); @Param("mid") String mid,
@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,4 +1,3 @@
package com.billpaymentservice.bbps.serviceImpl; package com.billpaymentservice.bbps.serviceImpl;
import java.util.Map; import java.util.Map;
@@ -23,12 +22,14 @@ 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;
@@ -53,6 +54,7 @@ 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
@@ -63,7 +65,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) { BillerMapperHelper mapperHelper,BillValidationRepository billValidationRepository) {
this.restClient = restClient; this.restClient = restClient;
this.httpService = httpService; this.httpService = httpService;
this.helper = helper; this.helper = helper;
@@ -71,6 +73,7 @@ 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
@@ -95,7 +98,13 @@ public class AgentServiceImpl implements AgentService {
OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, fullUrl, OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, fullUrl,
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,
@@ -115,26 +124,13 @@ public class AgentServiceImpl implements AgentService {
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
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());
@@ -147,13 +143,15 @@ 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);
throw new CustomException("Invalid JSON received from API"); // Use standardized user-facing message and status, keep internal details in remarks
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -171,6 +169,8 @@ 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)));
@@ -192,21 +192,24 @@ 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, userId); mapperHelper.saveOrUpdateBillData(responseWrapper, billFetchByBillIdRequest, txnId, reqId,billFetchByBillIdRequest.getClientUserId(), existingbill);
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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -255,7 +258,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;
@@ -265,7 +268,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -294,8 +297,14 @@ 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)));
@@ -314,24 +323,11 @@ public class AgentServiceImpl implements AgentService {
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
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());
@@ -349,7 +345,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS ,BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -362,7 +358,11 @@ public class AgentServiceImpl implements AgentService {
String reqId = billRequest.getReqId(); String reqId = billRequest.getReqId();
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);
@@ -392,19 +392,20 @@ public class AgentServiceImpl implements AgentService {
FetchBillValidateResponse responseWrapper = objectMapper.convertValue(dataMap, FetchBillValidateResponse responseWrapper = objectMapper.convertValue(dataMap,
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); mapperHelper.saveBillValidation(responseWrapper, billRequest, txnId,billValidation);
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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -456,7 +457,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(),
@@ -465,17 +466,10 @@ 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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
// ================================== 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) {
@@ -494,7 +488,12 @@ 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()) {
throw new CustomException(BBPSConstant.NO_BILL_FOUND); new CustomException(
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();
@@ -531,7 +530,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -552,7 +551,12 @@ 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(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"
);
} }
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();
@@ -588,7 +592,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -75,19 +75,9 @@ public class AuthServiceImpl implements AuthService {
String mobileNo = request.getMobile(); String mobileNo = request.getMobile();
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,6 +15,7 @@ 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;
@@ -103,6 +104,7 @@ 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);
@@ -125,7 +127,7 @@ public class ComplainServiceImpl implements ComplainService {
try { try {
Optional<TransactionMaster> dataById = transactionMasterRepository.findById(complainRequestDTO.getTxnId()); Optional<TransactionMaster> dataById = transactionMasterRepository.findByBankTxnId(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);
@@ -133,7 +135,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.getCreationDate(); String transactionCreatedDateString = transData.getCreationDateBank();
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));
@@ -195,7 +197,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); txnId, userId, requestTxnId, mid);
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);
@@ -207,7 +209,8 @@ 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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED",
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -290,7 +293,8 @@ 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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED",
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -356,7 +360,8 @@ 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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED",
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -420,7 +425,8 @@ 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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED",
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -532,7 +538,8 @@ 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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED",
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -599,7 +606,8 @@ 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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED",
BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -655,14 +663,15 @@ 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(complaintUserData.getContent().size()); String count = String.valueOf(dtoList.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, complaintUserData.getContent(), // Only the list of complaints (data) BBPSConstant.HTTP_OK_INT, dtoList, dtoList.size());
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,20 +181,15 @@ 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,7 +164,8 @@ 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("Invalid JSON received from API"); // Use user-friendly message constant and keep internal details in remarks
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
@@ -227,7 +228,8 @@ 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("Invalid JSON received from API"); // Unified constant for invalid JSON
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -298,7 +300,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -364,7 +366,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -431,7 +433,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -507,7 +509,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -570,7 +572,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -636,7 +638,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -700,7 +702,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -757,7 +759,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS , BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -829,7 +831,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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -887,8 +889,8 @@ 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("Invalid JSON received from API"); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
} }
@@ -1,5 +1,6 @@
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;
@@ -12,6 +13,7 @@ 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;
@@ -23,14 +25,12 @@ 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,7 +54,6 @@ 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;
@@ -66,8 +65,7 @@ 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, PaymentIntiateRepository paymentIntiateRepository, OutboundTraceLogRepository outboundTraceLogRepository, TransactionMasterRepository transactionMasterRepository,
TransactionMasterRepository transactionMasterRepository,
AgentFundTransferRepository agentFundTransferRepository, BillerMapperHelper mapperHelper) { AgentFundTransferRepository agentFundTransferRepository, BillerMapperHelper mapperHelper) {
this.beneficiaryDetailsRepository = beneficiaryDetailsRepository; this.beneficiaryDetailsRepository = beneficiaryDetailsRepository;
this.httpService = httpService; this.httpService = httpService;
@@ -75,7 +73,6 @@ 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;
@@ -108,92 +105,74 @@ 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);
try { TransactionMaster preTransaction=null;
LOGGER.info("Calling BBPS payment URL: {}", url); preTransaction = new TransactionMaster();
Map<String, String> responseMap = httpService.postRequest(url, forwardjson, headers); preTransaction.setTxnId(txnId);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); preTransaction.setInitiatorTxnId(txnId);
LOGGER.info("Payment API Response: {}", responseData); preTransaction.setReqId(requestId);
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);
helper.updateOutboundLogResponse(outboundLog, responseData); LOGGER.info("Calling BBPS payment URL: {}", url);
LOGGER.debug("Outbound log response updated for txnId={}", txnId); Map<String, String> responseMap = httpService.postRequest(url, forwardjson, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Payment API Response: {}", responseData);
if (responseData == null || responseData.isEmpty()) { helper.updateOutboundLogResponse(outboundLog, responseData);
helper.updateInboundLogFailure(inboundLog, BBPSConstant.EMPTY_RESPONSE_FROM_BBPS, LOGGER.debug("Outbound log response updated for txnId={}", txnId);
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());
}
BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class); Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog);
paymentRepository.save(entity);
LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId); BillPaymentResponse billPaymentResponse = objectMapper.convertValue(dataMap, BillPaymentResponse.class);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus()); GeneralResp generalResponse = billPaymentResponse.getGenericResponse();
PaymentInitiate paymentInitiate = new PaymentInitiate(); LOGGER.info("genric Response" + generalResponse);
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);
TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest(); BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class);
transactionStatusRequest.setTxnId(billPaymentResponse.getTxnId()); paymentRepository.save(entity);
transactionStatusRequest.setReqId(requestId); LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId);
transactionStatusRequest.setMid(billPaymentRequest.getMid());
transactionStatusRequest.setClientUserId(billPaymentRequest.getClientUserId());
try { helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus());
Thread.sleep((long) BBPSConstant.PAYMENT_STATUS_DELAY_MS);
} catch (InterruptedException e) { preTransaction.setBankTxnId(entity.getTxnId());
Thread.currentThread().interrupt(); transactionMasterRepository.save(preTransaction);
LOGGER.warn("Thread interrupted during payment delay: {}", e.getMessage()); LOGGER.info("updated TransactionMaster with bankTxnId for txnId={}", txnId);
}
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); TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest();
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); transactionStatusRequest.setReqId(billPaymentRequest.getReqId());
throw new CustomException("Invalid JSON received from API"); transactionStatusRequest.setTxnId(entity.getTxnId());
} ResponseModel responseModel = checkTrasactionStatus(transactionStatusRequest, inboundLog);
return responseModel;
} }
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 txnId = helper.generateTxnId(15); String checkerTxnId = 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, txnId, requestId, LOGGER.info("[{}] START (internal) | checkTxnId={} | reqId={} | bankTxnId={}", apiName, checkerTxnId, requestId,
transactionStatusRequest.getTxnId()); transactionStatusRequest.getTxnId());
String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId();
@@ -204,132 +183,142 @@ 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(txnId, requestId, apiName, url, BBPSConstant.HTTP_GET, OutboundTraceLog outboundLog = helper.createOutboundLog(checkerTxnId, requestId, apiName, url, BBPSConstant.HTTP_GET,
null); null);
LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", txnId, requestId); LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", checkerTxnId, 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={}", txnId); LOGGER.debug("Outbound log response updated for internal status check | txnId={}", checkerTxnId);
try { Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog);
Map<String, Object> dataMap = objectMapper.readValue(responseData, if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
new TypeReference<Map<String, Object>>() { helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE);
}); LOGGER.warn("Invalid transaction status response for txnId={}", transactionStatusRequest.getTxnId());
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE); Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
LOGGER.warn("Invalid transaction status response for txnId={}", transactionStatusRequest.getTxnId()); String.valueOf(dataMap.get("remarks")));
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);
BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); // First, attempt to find an existing TransactionMaster by initiatorTxnId and update it
HttpHeaders headers = helper.buildHeaders(headersData); String initiatorTxnId = transactionStatusRequest.getTxnId();
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.
}
InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName, LOGGER.info("Falling back to external BBPS status check URL={}", url);
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);
Map<String, String> responseMap = httpService.getRequest(url, headers); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); HttpHeaders headers = helper.buildHeaders(headersData);
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);
try { InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName,
Map<String, Object> dataMap = objectMapper.readValue(responseData, transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson);
new TypeReference<Map<String, Object>>() { LOGGER.debug("Inbound log created for public status check | reqId={} | txnId={}", requestId, txnID);
}); OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET,
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { null);
helper.updateInboundLogFailure(inboundLog, responseData, LOGGER.debug("Outbound log created for public status check | reqId={} | txnId={}", requestId, txnID);
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, Map<String, String> responseMap = httpService.getRequest(url, headers);
TransactionStatusResponse.class); String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
GeneralResp generalResp = responseWrapper.getGeneralResp(); 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);
String status = generalResp.getStatus(); Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog);
LOGGER.info("Current transaction status: {}", status); if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
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")));
}
ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
Integer.parseInt(generalResp.getStatusCode()), responseWrapper); TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); String status = generalResp.getStatus();
LOGGER.info("[{}] END (public) | reqId={} | txnId={} | status={}", apiName, requestId, txnID, LOGGER.info("Current transaction status: {}", status);
generalResp.getStatus());
return responseModel;
} catch (JsonProcessingException e) { // --- NEW: attempt to persist/update TransactionMaster so DB reflects new status/bankTxnId ---
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage(), e); TransactionDTO txDao = responseWrapper.getTransactionDAO();
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); TransactionMaster updatedEntity = null;
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(
@@ -345,68 +334,92 @@ 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);
Optional<PaymentInitiate> paymentInitiate = paymentIntiateRepository.findByReqId(clientReqId); // look up TransactionMaster(s) by clientReqId instead of PaymentInitiate
if (paymentInitiate.isEmpty()) { List<TransactionMaster> transactionMasters = transactionMasterRepository.findByClientReqId(clientReqId);
throw new CustomException(BBPSConstant.NO_BILL_FOUND); if (transactionMasters == null || transactionMasters.isEmpty()) {
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
+ paymentInitiate.get().getTxnId(); + chosen.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 {
LOGGER.info("Calling BBPS Transaction Status API URL={}", url); return objectMapper.readValue(responseData, new TypeReference<Map<String, Object>>() {});
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 in transaction status API: {}", e.getMessage(), e); LOGGER.error("Error parsing JSON response: {}", 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,7 +87,6 @@ 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
@@ -102,7 +101,7 @@ public class PaymentStartServiceImpl implements PaymentStartService {
sent.setEnckey(paymentEncryKey); sent.setEnckey(paymentEncryKey);
String plaintext = helper.convertObjectToJson(sent); String plaintext = helper.convertObjectToJson(sent);
String encrypted = encryptionUtil.encryptMData(plaintext, paymentEncryKey); String encrypted = encryptionUtil.encryptMData(plaintext, paymentEncryKey);
PaymentRequestPayload finalPayload = new PaymentRequestPayload(); PaymentRequestPayload finalPayload = new PaymentRequestPayload();
finalPayload.setMid(paymentMid); finalPayload.setMid(paymentMid);
finalPayload.setPayload(encrypted); finalPayload.setPayload(encrypted);
@@ -111,9 +110,13 @@ 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)));
@@ -124,12 +127,15 @@ 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);
} }
@@ -158,8 +164,7 @@ 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 = (apiResponse.getResponseData() != null && apiResponse.getResponseData().getQrString() != null && !apiResponse.getResponseData().getQrString().isEmpty()) ? BBPSConstant.RESPONSE_TYPE_QR : BBPSConstant.RESPONSE_TYPE_JSON; String responseType = BBPSConstant.RESPONSE_TYPE_INTENT;
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);
@@ -211,88 +216,92 @@ 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());
LOGGER.info("[startWebPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); helper.validateMerchant(request.getMid());
helper.validateMerchant(request.getMid()); // inbound log
String requestJson = helper.convertObjectToJson(request);
// inbound log InboundTraceLog inboundLog =
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 = Map<String, String> responseMap = httpService.postFormRequest(BBPSConstant.UAT_PAYMENT_URL_WEB, formPayload);
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);
throw new CustomException("Upstream HTTP Error", BBPSConstant.FAILED_STR, httpCode, respBody); paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR);
} 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);
throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PG, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR);
} 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_HTML; String responseTypeWeb = BBPSConstant.RESPONSE_TYPE_WEB;
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
com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse apiResp = new com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse(); PaymentStartApiResponse apiResp = new PaymentStartApiResponse();
com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse.ResponseData rd = new com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse.ResponseData(); PaymentStartApiResponse.ResponseData rd = new 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");
@@ -303,7 +312,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("HTML_RESPONSE"); rd.setQrString(BBPSConstant.RESPONSE_TYPE_WEB);
rd.setCreatedOn(LocalDateTime.now().toString()); rd.setCreatedOn(LocalDateTime.now().toString());
apiResp.setResponseData(rd); apiResp.setResponseData(rd);
@@ -353,14 +362,8 @@ public class PaymentStartServiceImpl implements PaymentStartService {
LOGGER.info("[{}] Decrypted data for mid={}", apiName, mid); LOGGER.info("[{}] Decrypted data for mid={}", apiName, mid);
// Parse decrypted JSON // Parse decrypted JSON (convert checked Jackson exception into unchecked CustomException so global handler can catch it)
Map<String, Object> payloadMap; Map<String, Object> payloadMap = parseJsonToMap(decrypted, apiName);
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", "")));
@@ -397,12 +400,9 @@ 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(java.time.LocalDateTime.now().toString()); record.setCallbackReceivedOn(LocalDateTime.now().toString());
String merchantStatus; String merchantStatus;
if (!signatureMatches) { if (!signatureMatches) {
@@ -437,30 +437,44 @@ 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<>();
try { Document doc = Jsoup.parse(html);
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,7 +23,6 @@ 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.findByMidOrClientUserId(mid, clientUserId, pageable); dataResult = transactionMasterRepository.findByMidAndUserId(mid, clientUserId, pageable);
} }
// List<TransactionMaster> dataResult = // List<TransactionMaster> dataResult =
@@ -109,7 +109,6 @@ 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,11 +18,10 @@ 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";
@@ -131,6 +130,7 @@ 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,7 +147,6 @@ 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";
@@ -196,6 +195,9 @@ 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";
@@ -220,5 +222,10 @@ 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,6 +1,7 @@
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 USER_IS_DELTED_IN_DB =602; public static final int INVALID_JSON_STATUS_CODE = 603;
} }
@@ -2,6 +2,7 @@ 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;
@@ -27,6 +28,9 @@ 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;
@@ -38,11 +42,10 @@ 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;
@@ -58,8 +61,6 @@ 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;
@@ -119,8 +120,6 @@ 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 {
@@ -576,87 +575,6 @@ 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) {
@@ -719,8 +637,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
LocalDateTime parentCreatedOn = parseToLocalDateTime(responseWrapper.getCreationDate()); Date parentCreatedOn = parseToDate(responseWrapper.getCreationDate());
LocalDateTime parentUpdatedOn = parseToLocalDateTime(responseWrapper.getModifiedDate()); Date parentUpdatedOn = parseToDate(responseWrapper.getModifiedDate());
parent.setCreatedEpochDate(responseWrapper.getCreatedEpochDate()); parent.setCreatedEpochDate(responseWrapper.getCreatedEpochDate());
parent.setModifiedEpochDate(responseWrapper.getModifiedEpochDate()); parent.setModifiedEpochDate(responseWrapper.getModifiedEpochDate());
parent.setReqId(reqId); parent.setReqId(reqId);
@@ -900,9 +818,8 @@ public class BillerMapperHelper {
} }
public void saveOrUpdateBillData(BillDataResponse responseWrapper, public void saveOrUpdateBillData(BillDataResponse responseWrapper,
BillFetchByBillIdRequest billFetchByBillIdRequest, String txnId, String reqId, String userId) { BillFetchByBillIdRequest billFetchByBillIdRequest, String txnId, String reqId, String userId,BillMaster billMaster) {
BillMaster billMaster = new BillMaster();
billMaster.setBillId(responseWrapper.getBillId()); billMaster.setBillId(responseWrapper.getBillId());
billMaster.setRefId(responseWrapper.getRefId()); billMaster.setRefId(responseWrapper.getRefId());
@@ -962,9 +879,9 @@ public class BillerMapperHelper {
// DATES // DATES
// Map incoming DTO date strings into Auditable LocalDateTime fields // Map incoming DTO date strings into Auditable LocalDateTime fields
LocalDateTime bmCreatedOn = parseToLocalDateTime(responseWrapper.getCreationDate()); Date bmCreatedOn = parseToDate(responseWrapper.getCreationDate());
LocalDateTime bmUpdatedOn = parseToLocalDateTime(responseWrapper.getModifiedDate()); Date bmUpdatedOn = parseToDate(responseWrapper.getModifiedDate());
if (bmUpdatedOn != null) { if (bmUpdatedOn != null) {
billMaster.setUpdatedDateTime(bmUpdatedOn); billMaster.setUpdatedDateTime(bmUpdatedOn);
} }
@@ -974,29 +891,23 @@ public class BillerMapperHelper {
billMasterRepository.save(billMaster); billMasterRepository.save(billMaster);
} }
// Helper to parse incoming ISO date/time strings (with optional offset) into LocalDateTime // Helper to parse incoming ISO date/time strings (with optional offset) into
private LocalDateTime parseToLocalDateTime(String dateStr) { // LocalDateTime
if (dateStr == null || dateStr.isBlank()) { private Date parseToDate(String dateStr) {
return null; try {
} // example format: 2025-12-17T14:30:45
try { LocalDateTime ldt = LocalDateTime.parse(dateStr);
// Try parsing with offset first (e.g., 2023-04-19T17:28:05.508679Z or with +05:30) return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
OffsetDateTime odt = OffsetDateTime.parse(dateStr); } catch (Exception e) {
return odt.toLocalDateTime(); return null;
} 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) return null; if (transactionMaster == 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());
@@ -1033,20 +944,22 @@ 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.getCreationDate()); dto.setCreationDate(transactionMaster.getCreationDateBank());
dto.setModifiedDate(transactionMaster.getModifiedDate()); dto.setModifiedDate(transactionMaster.getModifiedDateBank());
dto.setCreatedEpochDate(transactionMaster.getCreatedEpochDate()); dto.setCreatedEpochDate(transactionMaster.getCreatedEpochDateBank());
dto.setModifiedEpochDate(transactionMaster.getModifiedEpochDate()); dto.setModifiedEpochDate(transactionMaster.getModifiedEpochDate());
return dto; return dto;
} }
// Convert TransactionMaster to particular details DTO (used with method references) // Convert TransactionMaster to particular details DTO (used with method
// references)
public TransactionHistoryParticularDetailsDTO billerRemainingDetails(TransactionMaster transactionMaster) { public TransactionHistoryParticularDetailsDTO billerRemainingDetails(TransactionMaster transactionMaster) {
if (transactionMaster == null) return null; if (transactionMaster == 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.getCreationDate()); dto.setCreationDate(transactionMaster.getCreationDateBank());
dto.setBillerCategory(transactionMaster.getBillerCategory()); dto.setBillerCategory(transactionMaster.getBillerCategory());
dto.setBillerName(transactionMaster.getBillerName()); dto.setBillerName(transactionMaster.getBillerName());
dto.setStatus(transactionMaster.getStatus()); dto.setStatus(transactionMaster.getStatus());
@@ -1078,39 +991,74 @@ public class BillerMapperHelper {
return forward; return forward;
} }
// Map PaymentStartUserRequest + API response into PaymentStartRecord and persist // Map PaymentStartUserRequest + API response into PaymentStartRecord and
public void savePaymentStartRecord(PaymentStartUserRequest request, PaymentStartApiResponse apiResponse, // persist
String txnId, String orderNo, String requestJson, String rawResponse, String responseType) { public PaymentStartRecord savePaymentStartRecord(
PaymentStartRecord record = new PaymentStartRecord(); PaymentStartUserRequest request,
record.setOrderNo(orderNo); PaymentStartApiResponse apiResponse,
record.setRequestId(request.getReqId()); String txnId,
record.setClientMid(request.getMid()); String orderNo,
record.setClientUserId(request.getClientUserId()); String requestJson,
record.setInternalTxnId(txnId); String rawResponse,
String responseType) {
if (apiResponse != null && apiResponse.getResponseData() != null) { PaymentStartRecord record;
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());
record.setTxnStatus("PENDING");
} else {
record.setAmount("0.00");
record.setMid(request.getMid());
record.setTxnStatus("PENDING");
}
// store the original request body and raw response boolean createNewRecord =
record.setRequestBody(requestJson); apiResponse == null &&
record.setResponseBody(rawResponse); rawResponse == null &&
record.setResponseType(responseType); responseType == null;
paymentStartRecordRepository.save(record); if (createNewRecord) {
// 🔒 Duplicate check
if (paymentStartRecordRepository.findByOrderNo(orderNo).isPresent()) {
throw new CustomException(
"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) {
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();
@@ -1129,9 +1077,11 @@ public class BillerMapperHelper {
return forward; return forward;
} }
// Check if complaint can be raised (true if within 24 hours of transactionCreatedDateString) // Check if complaint can be raised (true if within 24 hours of
// transactionCreatedDateString)
public static boolean isComplaintAllowed(String transactionCreatedDateString) { public static boolean isComplaintAllowed(String transactionCreatedDateString) {
if (transactionCreatedDateString == null || transactionCreatedDateString.isBlank()) return false; if (transactionCreatedDateString == null || transactionCreatedDateString.isBlank())
return false;
try { try {
OffsetDateTime transactionCreatedDate = OffsetDateTime.parse(transactionCreatedDateString); OffsetDateTime transactionCreatedDate = OffsetDateTime.parse(transactionCreatedDateString);
OffsetDateTime currentTime = OffsetDateTime.now(); OffsetDateTime currentTime = OffsetDateTime.now();
@@ -1144,9 +1094,33 @@ 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 userId, String requestTxnId, String mid) {
if (responseWrapper == null || responseWrapper.getTicketDetails() == null) { if (responseWrapper == null || responseWrapper.getTicketDetails() == null) {
return "No data"; return "No data";
} }
@@ -1158,7 +1132,9 @@ 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());
@@ -1175,33 +1151,10 @@ 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) return null; if (req == 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());
@@ -1213,10 +1166,11 @@ public class BillerMapperHelper {
return f; return f;
} }
public void saveBillValidation(com.billpaymentservice.bbps.dto.response.FetchBillValidateResponse responseWrapper, public void saveBillValidation(FetchBillValidateResponse responseWrapper,
com.billpaymentservice.bbps.dto.request.BillFetchByBillIdRequest billRequest, String txnId) { BillFetchByBillIdRequest billRequest, String txnId,BillValidation entity) {
if (responseWrapper == null || billRequest == null) return; if (responseWrapper == null || billRequest == null)
BillValidation entity = new BillValidation(); return;
entity.setBillId(responseWrapper.getBillId()); entity.setBillId(responseWrapper.getBillId());
entity.setRefId(responseWrapper.getRefId()); entity.setRefId(responseWrapper.getRefId());
entity.setBillerId(responseWrapper.getBillerId()); entity.setBillerId(responseWrapper.getBillerId());
@@ -1230,7 +1184,8 @@ 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.getAdditionalInfo()) { for (com.billpaymentservice.bbps.dto.AdditionalInfoBillValidateDTO ai : responseWrapper
.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());
@@ -1262,4 +1217,66 @@ 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;
}
}
@@ -1,20 +0,0 @@
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,7 +29,6 @@ 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,10 +14,13 @@ 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;
@@ -30,7 +33,7 @@ import com.billpaymentservice.bbps.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@Component @Component
public class Helper extends ConfigHelper { public class Helper {
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;
@@ -225,5 +228,68 @@ public class Helper extends ConfigHelper {
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,6 +55,9 @@ 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
+7 -1
View File
@@ -21,7 +21,7 @@ spring.profiles.active=local
# DataSource # DataSource
spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_new_updated spring.datasource.url=jdbc:postgresql://localhost:5432/devdbuat
spring.datasource.username=postgres spring.datasource.username=postgres
#spring.datasource.password=UAT@DB_2025 #spring.datasource.password=UAT@DB_2025
@@ -72,7 +72,13 @@ 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
@@ -1,189 +0,0 @@
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());
}
}
}
}
@@ -1,109 +0,0 @@
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());
}
}