FraudEngine.java
Bridget/payment-gateway-microservices/services/fraud-check-service/src/main/java/com/checkout/paymentgw/fraud/engine/FraudEngine.java
package com.checkout.paymentgw.fraud.engine;
import com.checkout.paymentgw.fraud.api.FraudCheckController.Decision;
import com.checkout.paymentgw.fraud.api.FraudCheckController.ScreenRequest;
import com.checkout.paymentgw.fraud.domain.BlockedBin;
import com.checkout.paymentgw.fraud.domain.BlockedBinRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
public class FraudEngine {
private final BlockedBinRepository blockedBins;
private final StringRedisTemplate redis;
private final int velocityLimit;
private final Duration velocityWindow;
private final int amountThreshold;
public FraudEngine(BlockedBinRepository blockedBins,
StringRedisTemplate redis,
@Value("${fraud.velocity.limit:5}") int velocityLimit,
@Value("${fraud.velocity.window-seconds:60}") long windowSeconds,
@Value("${fraud.amount.threshold:1000000}") int amountThreshold) {
this.blockedBins = blockedBins;
this.redis = redis;
this.velocityLimit = velocityLimit;
this.velocityWindow = Duration.ofSeconds(windowSeconds);
this.amountThreshold = amountThreshold;
}
public Decision evaluate(String merchantId, ScreenRequest request) {
List<String> reasons = new ArrayList<>();
String decision = "ALLOW";
// 1. BIN blacklist
String bin = request.cardNumber().substring(0, Math.min(6, request.cardNumber().length()));
Optional<BlockedBin> hit = blockedBins.findById(bin);
if (hit.isPresent()) {
reasons.add("BIN_BLOCKED:" + bin);
decision = "BLOCK";
}
// 2. Velocity (per-merchant attempts in a sliding window)
long count = bumpAndGetVelocity(merchantId);
if (count > velocityLimit) {
reasons.add("VELOCITY_EXCEEDED:" + count);
if (!"BLOCK".equals(decision)) decision = "REVIEW";
}
// 3. Amount threshold
if (request.amount() != null && request.amount() > amountThreshold) {
reasons.add("AMOUNT_THRESHOLD:" + request.amount());
if (!"BLOCK".equals(decision)) decision = "REVIEW";
}
return new Decision(decision, reasons);
}
private long bumpAndGetVelocity(String merchantId) {
String key = "vel:" + merchantId;
Long c = redis.opsForValue().increment(key);
if (c != null && c == 1L) {
redis.expire(key, velocityWindow);
}
return c == null ? 0L : c;
}
}
Bài viết liên quan
PaymentGatewayApplication.java
PaymentGatewayApplication.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/PaymentGatewayApplication.java).
Đọc bài viết →ApplicationConfiguration.java
ApplicationConfiguration.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/configuration/ApplicationConfiguration.java).
Đọc bài viết →RequestResponseLogger.java
RequestResponseLogger.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/configuration/RequestResponseLogger.java).
Đọc bài viết →PaymentGatewayController.java
PaymentGatewayController.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/controller/PaymentGatewayController.java).
Đọc bài viết →PaymentStatus.java
PaymentStatus.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/enums/PaymentStatus.java).
Đọc bài viết →BankErrorException.java
BankErrorException.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/exception/BankErrorException.java).
Đọc bài viết →