← Back to Documentation Portal|📊 Coverage Quality Gates|Coverage Dashboard
Payflow API — Automated JaCoCo Code Coverage (90% Line / 73% Branch)

UpiValidationService.java

package com.payflow.service;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;

import com.payflow.client.UpiValidationClient;
import com.payflow.client.UpiVerificationResponse;
import com.payflow.exception.InvalidUpiException;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;

@Service
public class UpiValidationService {

	private static final Logger LOG = LoggerFactory.getLogger(UpiValidationService.class);

	private final UpiValidationClient upiValidationClient;
	private final boolean validationEnabled;

	public UpiValidationService(UpiValidationClient upiValidationClient, //
			@Value("${payflow.upi-validation.enabled:false}") boolean validationEnabled) {
		this.upiValidationClient = upiValidationClient;
		this.validationEnabled = validationEnabled;
	}

	public void validateUpi(String upiId) {
		if (!validationEnabled) {
			LOG.debug("External UPI validation disabled. Skipping for {}", upiId);
			return;
		}

		UpiVerificationResponse response;
		try {
			response = executeValidationWithRetry(upiId);
		} catch (Exception ex) {
			LOG.warn("External validation failed for {}: {}. Proceeding with fallback.", //
					upiId, ex.getMessage());
			response = recoverFromValidationFailure(ex, upiId);
		}

		if (response != null && !response.valid()) {
			LOG.warn("External UPI validation failed for UPI ID: {}", upiId);
			throw new InvalidUpiException("UPI ID is invalid or rejected by external provider: " + upiId);
		}

		LOG.info("External UPI validation successful for: {}, bank: {}", upiId,
				response != null ? response.bankName() : "UNKNOWN");
	}

	@CircuitBreaker(name = "upiValidation", fallbackMethod = "recoverFromValidationFailure")
	@Retryable(retryFor = {RestClientException.class, IOException.class}, //
			maxAttempts = 3, //
			backoff = @Backoff(delay = 500, multiplier = 2.0, random = true))
	public UpiVerificationResponse executeValidationWithRetry(String upiId) {
		LOG.debug("Calling external UPI validation service for: {}", upiId);
		return upiValidationClient.verify(upiId);
	}

	@Recover
	public UpiVerificationResponse recoverFromValidationFailure(Exception ex, String upiId) {
		String msg = "All retries failed for UPI validation of " + upiId + ": " + ex.getMessage();
		LOG.warn("{}. Proceeding with graceful fallback.", msg);
		return new UpiVerificationResponse(true, "UNKNOWN (FALLBACK)", "UNKNOWN");
	}

	public UpiVerificationResponse recoverFromValidationFailure(String upiId, Throwable t) {
		String msg = "Circuit breaker fallback for UPI: " + upiId + ": " + t.getMessage();
		LOG.warn("{}. Proceeding with graceful fallback.", msg);
		return new UpiVerificationResponse(true, "UNKNOWN (FALLBACK)", "UNKNOWN");
	}

	public boolean isValidationEnabled() {
		return validationEnabled;
	}
}