Fraud Module¶
The fraud module provides the core fraud detection infrastructure. External scoring intelligence lives in extensions (ext_ipqs, ext_maxmind, ext_signifyd, ext_riskified); the core provides data models, rule orchestration, and admin UI.

Rules are edited from Settings → Fraud, with a separate blocklist tab:

Architecture¶
Fraud assessment runs in two phases during checkout:
- Pre-payment (before
process_payment): blocklist matching, velocity checks, address mismatch detection, proxy/VPN/Tor signals, and the extension'sscore_checkout()method. - Post-payment (after
process_payment): AVS/CVV rule evaluation and the extension'sscore_post_payment()method (for Signifyd/Riskified chargeback guarantee providers).
Scores from both phases are merged. The worst decision wins (block > review > allow).
Key Files¶
| File | Purpose |
|---|---|
modules/fraud/models.py | FraudRule, FraudBlocklist, CustomerRiskEvent |
modules/fraud/services.py | FraudAssessmentService — core rule engine |
modules/fraud/resolvers.py | GraphQL queries (dashboard, review queue) and mutations (approve/decline, rules CRUD, blocklist CRUD) |
modules/fraud/types.py | Strawberry GraphQL types |
modules/auth/strategies.py | FraudContext, FraudScore, FraudScoringStrategy |
modules/payment/strategies.py | PaymentResult (structured fraud fields) |
tasks/fraud.py | recalculate_customer_risk Temporal activity |
Models¶
FraudRule¶
Configurable rules evaluated at checkout. Channel-scoped (nullable channel_id).
| rule_type | Description |
|---|---|
velocity_ip | Max orders per IP in a time window |
velocity_email | Max orders per email in a time window |
velocity_customer | Max orders per customer in a time window |
velocity_value | Max total $ per customer in a time window |
avs_mismatch | Hold when AVS response indicates no match |
cvv_mismatch | Hold when CVV response indicates no match |
Config JSON: {"window_hours": 24, "threshold": 5}
FraudBlocklist¶
Blocked or flagged values. Checked during pre-payment assessment.
| entry_type | Example |
|---|---|
ip | 192.168.1.1 |
email | bad@example.com |
email_domain | tempmail.com |
country | RU |
postal_code | 90210 |
CustomerRiskEvent¶
Immutable audit log for fraud activity. Displayed on the customer detail page.
GraphQL API¶
Queries¶
fraudDashboard— aggregated metrics for the dashboard pagefraudReviewQueue(limit, offset)— orders pending fraud reviewfraudRules(channelId)— configured rulesfraudBlocklist(channelId, entryType)— blocklist entriescustomerRiskEvents(customerId, accountId, limit)— risk timeline
Mutations¶
approveFraudReview(orderId)— approve an order, transition to AwaitingFulfillmentdeclineFraudReview(orderId, reason)— decline and cancelcreateFraudRule(...)/updateFraudRule(...)/deleteFraudRule(...)— rule CRUDaddToFraudBlocklist(...)/removeFromFraudBlocklist(...)— blocklist CRUD
Permissions¶
| Codename | Scope |
|---|---|
fraud.review | View dashboard, review queue, approve/decline |
settings.edit | Manage rules and blocklist |
Order Risk Fields¶
Added to the orders table:
| Column | Type | Description |
|---|---|---|
risk_score | Decimal(5,4) | 0.0000 (safe) to 1.0000 (fraud) |
risk_level | String(10) | low, medium, high, critical |
risk_factors | JSONB | Array of factor objects |
avs_result | String(10) | AVS response code from gateway |
cvv_result | String(10) | CVV response code from gateway |
Building a Fraud Extension¶
Implement FraudScoringStrategy from modules/auth/strategies.py:
class MyFraudStrategy(FraudScoringStrategy):
@property
def provider_name(self) -> str:
return "my_provider"
async def score_login(self, context: FraudContext) -> FraudScore:
# IP-based login risk
...
async def score_checkout(self, context: FraudContext) -> FraudScore:
# Pre-payment scoring
...
async def score_post_payment(self, context: FraudContext) -> FraudScore:
# Post-auth scoring (for guarantee providers)
...
async def report_decision(self, order_id, decision, analyst_id=None):
# Feedback loop for ML training
...
Register in your extension's on_activate:
strategy_resolver.register(
FraudScoringStrategy,
MyProxyStrategy(),
name="fraud_scoring",
extension_name=self.name,
)
The FraudContext provides 30+ fields covering IP, geo, buyer identity, billing/shipping addresses, payment data, and order details. See the dataclass in modules/auth/strategies.py for the full list.
Affiliate Fraud Guard (Decided #179)¶
Affiliate commission attribution has its own multi-factor fraud guard layered on top of the order-level fraud signal. The guard evaluates four signals before crediting commission:
- Click → conversion latency — windows that are too tight or stale relative to the program's expected conversion curve
- IP correlation — buyer IP matches the affiliate's last-known login or referrer IP
- Account self-attribution — the buyer's billing email matches an affiliate-managed account
- Velocity — abnormal click density from a single source
A suspicious commission enters the Affiliate Review Queue (admin /affiliates/approvals). The affiliate sees the order in their dashboard but the commission is pending_review; on approve/deny the commission either posts to the ledger or is voided. The signals and decision are written to affiliate_commission_audit for appeal.
The guard is implemented in vectis.modules.affiliate.services.evaluate_fraud_signals(); thresholds are per-channel settings.