Skip to content
advanced Phase 89 · Payment Deep Dive

Fraud Prevention

Fraud detection, risk scoring, velocity checks, manual review systems

45m
0 problems
Topic Progress 0%

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?

Question 1 options

2. What do velocity checks monitor?

Question 2 options

3. What happens in manual review?

Question 3 options

Flashcards

Question

What is fraud risk scoring?

Answer

Weighted calculation of fraud indicators (0-100)

Question

What are velocity checks?

Answer

Monitoring order frequency and spending patterns

Question

What is manual review?

Answer

Admin examination of flagged high-risk orders

Question

What triggers fraud review?

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