Fraud Detection Architecture
Fraud Detection Flow
┌─────────────â”
│ Order │
│ Placed │
└──────┬──────┘
│
┌──────▼──────â”
│ Risk │
│ Score │
└──────┬──────┘
│
┌────┴────â”
â–¼ â–¼
┌───────┠┌───────────â”
│ Low │ │ High Risk │
│ Risk │ │ │
└───┬───┘ └─────┬─────┘
│ │
â–¼ â–¼
┌───────┠┌───────────â”
│ Auto │ │ Manual │
│ Approve│ │ Review │
└───────┘ └───────────┘
Risk Scoring Service
namespace Vendor\Fraud\Service;
class RiskScoringService
{
private array $rules;
private WeightCalculatorInterface $weightCalc;
public function calculateRisk(OrderInterface $order): RiskResult
{
$score = 0;
$triggeredRules = [];
foreach ($this->rules as $rule) {
if ($rule->matches($order)) {
$score += $rule->getWeight();
$triggeredRules[] = $rule->getName();
}
}
// Normalize to 0-100
$normalizedScore = min(100, max(0, $score));
return new RiskResult([
'score' => $normalizedScore,
'risk_level' => $this->getRiskLevel($normalizedScore),
'triggered_rules' => $triggeredRules,
'recommendation' => $this->getRecommendation($normalizedScore),
]);
}
private function getRiskLevel(int $score): string
{
return match(true) {
$score < 30 => 'low',
$score < 70 => 'medium',
default => 'high',
};
}
}
Risk Scoring Rules
Fraud Rule Interface
namespace Vendor\Fraud\Rule;
interface FraudRuleInterface
{
public function matches(OrderInterface $order): bool;
public function getWeight(): int;
public function getName(): string;
}
Rule Implementations
namespace Vendor\Fraud\Rule\Checks;
class HighValueOrderRule implements FraudRuleInterface
{
private float $threshold = 500;
public function matches(OrderInterface $order): bool
{
return $order->getGrandTotal() > $this->threshold;
}
public function getWeight(): int { return 20; }
public function getName(): string { return 'high_value_order'; }
}
class MismatchedAddressRule implements FraudRuleInterface
{
public function matches(OrderInterface $order): bool
{
$billing = $order->getBillingAddress();
$shipping = $order->getShippingAddress();
if (!$shipping) return false;
return $billing->getCountryId() !== $shipping->getCountryId();
}
public function getWeight(): int { return 30; }
public function getName(): string { return 'mismatched_addresses'; }
}
class NewCustomerHighValueRule implements FraudRuleInterface
{
private float $threshold = 200;
public function matches(OrderInterface $order): bool
{
$customer = $order->getCustomer();
$orderCount = $customer->getOrdersCount() ?? 0;
return $orderCount === 0 && $order->getGrandTotal() > $this->threshold;
}
public function getWeight(): int { return 25; }
public function getName(): string { return 'new_customer_high_value'; }
}
Velocity Checks
Velocity Checker
namespace Vendor\Fraud\Velocity;
class VelocityChecker
{
private CacheInterface $cache;
private int $maxOrdersPerHour = 5;
private int $maxOrdersPerDay = 10;
private int $maxAmountPerDay = 2000;
public function check(array $criteria): VelocityResult
{
$violations = [];
// Check orders per hour
$hourlyCount = $this->getCount(
'orders_hourly_' . $criteria['email'],
3600
);
if ($hourlyCount >= $this->maxOrdersPerHour) {
$violations[] = 'too_many_orders_hourly';
}
// Check orders per day
$dailyCount = $this->getCount(
'orders_daily_' . $criteria['email'],
86400
);
if ($dailyCount >= $this->maxOrdersPerDay) {
$violations[] = 'too_many_orders_daily';
}
// Check daily amount
$dailyAmount = $this->getAmount(
'amount_daily_' . $criteria['email'],
86400
);
if ($dailyAmount >= $this->maxAmountPerDay) {
$violations[] = 'daily_amount_exceeded';
}
return new VelocityResult(
empty($violations),
$violations
);
}
private function getCount(string $key, int $ttl): int
{
$count = (int) $this->cache->load($key);
return $count;
}
public function incrementCount(string $email, float $amount): void
{
// Hourly
$hourlyKey = 'orders_hourly_' . $email;
$this->increment($hourlyKey, 3600);
// Daily
$dailyKey = 'orders_daily_' . $email;
$this->increment($dailyKey, 86400);
// Daily amount
$amountKey = 'amount_daily_' . $email;
$this->addToAmount($amountKey, $amount, 86400);
}
}
Manual Review Workflow
Review Queue
namespace Vendor\Fraud\Review;
class ManualReviewService
{
private ReviewRepositoryInterface $reviewRepo;
private OrderRepositoryInterface $orderRepo;
public function addToReview(
OrderInterface $order,
RiskResult $riskResult
): ReviewInterface {
$review = new ReviewData([
'order_id' => $order->getId(),
'risk_score' => $riskResult->getScore(),
'triggered_rules' => $riskResult->getTriggeredRules(),
'status' => 'pending',
'created_at' => new \DateTime(),
]);
// Hold order
$order->setState(Order::STATE_HOLDED);
$order->addCommentToStatusHistory('Order placed in fraud review');
$this->orderRepo->save($order);
return $this->reviewRepo->save($review);
}
public function approve(int $reviewId, int $adminId, string $notes): void
{
$review = $this->reviewRepo->get($reviewId);
$order = $this->orderRepo->get($review->getOrderId());
$review->setStatus('approved');
$review->setReviewedBy($adminId);
$review->setReviewNotes($notes);
$this->reviewRepo->save($review);
$order->setState(Order::STATE_PROCESSING);
$order->addCommentToStatusHistory('Approved by fraud review: ' . $notes);
$this->orderRepo->save($order);
}
public function decline(int $reviewId, int $adminId, string $notes): void
{
$review = $this->reviewRepo->get($reviewId);
$order = $this->orderRepo->get($review->getOrderId());
$review->setStatus('declined');
$review->setReviewedBy($adminId);
$review->setReviewNotes($notes);
$this->reviewRepo->save($review);
$order->setState(Order::STATE_CANCELED);
$order->addCommentToStatusHistory('Declined by fraud review: ' . $notes);
$this->orderRepo->save($order);
}
}
Quiz
1. What is risk scoring?
2. What do velocity checks monitor?
3. What happens in manual review?
Flashcards
Question
What is fraud risk scoring?
Click to reveal answer
Answer
Weighted calculation of fraud indicators (0-100)
Question
What are velocity checks?
Click to reveal answer
Answer
Monitoring order frequency and spending patterns
Question
What is manual review?
Click to reveal answer
Answer
Admin examination of flagged high-risk orders
Question
What triggers fraud review?
Click to reveal answer
Answer
Risk score exceeding threshold or velocity violation
Revision Notes
Key Takeaways
- 1. Risk scoring combines multiple fraud indicators into a score
- 2. Velocity checks detect unusual order frequency patterns
- 3. Manual review holds orders for admin examination
- 4. Fraud rules are configurable and weighted
- 5. Risk levels determine auto-approve vs manual review
Interview Tips
- • Explain how risk scoring algorithms work
- • Discuss velocity check configurations
- • Describe the manual review workflow
- • Talk about balancing fraud prevention with customer experience
Cheat Sheet
Fraud Detection:
Risk Score → 0-100 (low/medium/high)
Velocity → orders/hour, orders/day, amount/day
Rules → weighted fraud indicators
Workflow:
Low Risk → Auto Approve
Medium Risk → Additional checks
High Risk → Manual Review
Review:
Hold order → Admin examines → Approve/Decline
Common Rules:
high_value_order, mismatched_addresses
new_customer_high_value, velocity_exceeded