Skip to content

Fraud Prevention — Velocity Limits, Blocklists & Alerts

Validation legend: ✅ verified in code · 📄 Excel/spec-only · 💻 code-only · ♻️ duplicate · ⚰️ dead code · ⚠️ risk/known-issue

[!NOTE] Fraud Prevention protects the loyalty economy: it stops customers from farming/burning points faster than allowed, and lets clients blocklist bad actors. It is tightly coupled to Loyalty — every point allocation and redemption calls into fraudRulesController.checkCustomerIsBlocked / checkCustomerFraud. Roadmap A2.2 merges this module into loyalty as "Loyalty & Trust" (P1).


1. Overview

Two enforcement mechanisms + alerting:

  1. Velocity limits (checkCustomerFraud) — cap redemptions and point-earning events per period (hour/day/week) per customer, configured in MySQL wallet_rules. Breaching flags the customer into MongoDB fraudulent_customers.
  2. Blocklist (checkCustomerIsBlocked) — hard block by phone number (manual entry or CSV bulk upload), stored in blocklist_contacts and reflected on customers.is_blocked.
  3. Alerts (fraud_alert_settings + sendAlert) — notify configured recipients (SMS/WhatsApp) when fraud is detected; the send debits the business wallet.

fraudRulesController exports (✅): configureFraudWalletRules, configureFraudAlertSettings, getFraudConfiguration, addBlocklistContacts, blockUnblockAndResolveContacts, markAndResolveContacts, deleteBlocklistContacts, listBlocklistContacts, checkCustomerFraud, sendAlert, checkCustomerIsBlocked, getCustomerBlocked, evaluateCustomerFraud, sendAlertForFraudCustomer, viewFraudulentCustomer, groupListFraudContacts, fraudListCustomersMis.


2. Business Flow

2.1 Example journey

flowchart LR
    A[Admin sets velocity limits<br/>configureFraudWalletRules] --> B[Customer earns/redeems]
    B --> C[Loyalty engine calls<br/>checkCustomerIsBlocked + checkCustomerFraud]
    C --> D{limit breached?}
    D -->|no| E[Allow]
    D -->|yes| F[Flag fraudulent_customers<br/>fraud_type + block_reason]
    F --> G[sendAlert SMS/WA<br/>debits business wallet]
    G --> H[Admin reviews →<br/>block / unblock / resolve]

2.2 Edge cases

Case Rule Validation
Redemption over limit count txn_type=2 in period > redemption_limit_value → flag "Redemption Frequency Limit"
Earning over limit count txn_type=1 in period > points_earning_limit_value → flag "Points Earning Limit"
Blocklisted phone checkCustomerIsBlocked returns null → loyalty action denied
CSV upload garbage phone must be 10 digits, not starting 0–5 (landline filter); batch 1000
System vs manual flag blocked_by = "System" (auto) or user id (manual)
Alert with low wallet send skipped if wallet_balance < channel_cost
Block/unblock status 2=block (customers.is_blocked=1), 3=unblock; both resolve the flag

3. Technical Flow

CONFIG:   configureFraudWalletRules → wallet_rules (redemption/earning limit value+period)
          configureFraudAlertSettings → fraud_alert_settings (recipients, channel, template)
          addBlocklistContacts (CSV/manual) → blocklist_contacts
ENFORCE (called by Loyalty engine on every earn/redeem):
  checkCustomerIsBlocked(parent_business_id, mobileNo, customer_details) → null if blocked
  checkCustomerFraud(connection, wallet_rules, wallet_id, parent_business_id, child_business_id, mobileNo, type)
     type="redemption" → count wallet_history txn_type=2 in period vs redemption_limit_value
     type="allocation"  → count wallet_history txn_type=1 in period vs points_earning_limit_value
     breach → insert fraudulent_customers {fraud_type, block_reason, blocked_by:"System", status:1}
ALERT:    sendAlert(parent_business_id, db, contacts)
     SMS: deduct sms_cost+gst → sms_queue_YYYYMMDD + wallet_transactions
     WA:  deduct whatsapp_cost/utility+gst → whatsapp_queue_YYYYMMDD + wallet_transactions
REVIEW:   blockUnblockAndResolveContacts (status 2 block / 3 unblock) → customers.is_blocked ; fraudulent_customers.status=2
MIS:      fraudListCustomersMis, groupListFraudContacts, viewFraudulentCustomer

Related revocation crons (protect the economy): - posRedemptionRevokeCron (/5 min) — reverts POS loyalty/coupon redemptions where the POS order never arrived (pending_redemptions/pending_coupon_redemptions past expires_at) → restores points, wallet_history status=2. - whatsappRefund* (00:00) — refunds failed WhatsApp sends (also touches fraud-alert sends).


4. Architecture Diagram

flowchart TB
    subgraph cfg["Config (Admin)"]
        WR[(wallet_rules<br/>redemption/earning limits + period)]
        FAS[(fraud_alert_settings<br/>recipients/channel/template)]
        BL[(blocklist_contacts<br/>manual + CSV)]
    end
    subgraph enforce["Enforcement (per loyalty action)"]
        CIB[checkCustomerIsBlocked] --> BL
        CCF[checkCustomerFraud] --> WR
        CCF --> WH[(wallet_history<br/>count txn_type by period)]
    end
    LOY[Loyalty earn/redeem] --> CIB
    LOY --> CCF
    CCF -->|breach| FC[(fraudulent_customers<br/>fraud_type, blocked_by System)]
    FC --> AL[sendAlert SMS/WA]
    AL --> WT[(wallet_transactions debit)]
    FC --> REV[Admin review<br/>block/unblock/resolve]
    REV --> CUST[(customers.is_blocked)]
    PRC[posRedemptionRevokeCron */5] --> WH
stateDiagram-v2
    [*] --> Clean
    Clean --> Flagged: velocity breach (status=1, blocked_by=System)
    Flagged --> Blocked: admin status=2 (is_blocked=1)
    Flagged --> Resolved: admin status=2 (resolve, comment)
    Blocked --> Unblocked: admin status=3 (is_blocked unset)
    Unblocked --> Clean

5. Folder Structure (real files)

uengage-crm/
├── controllers/
│   └── fraudRulesController.js   ✅ config, blocklist, checkCustomerFraud, checkCustomerIsBlocked, alerts, MIS
├── routes/prism_routes/
│   └── fraud_rules_routes.js     ✅ all fraud routes (authMiddleware; upload uses file middleware)
├── crons/
│   ├── posRedemptionRevokeCron.js   ✅ */5 revert stale POS redemptions/coupons
│   └── whatsappRefund.js            ✅ 00:00 refund failed WA sends
└── (consumers) loyaltyController.js, prismIntegration.js, posistLoyaltyIntegration.js, handleWalletPointsFlow.js
                                     ✅ all call checkCustomerIsBlocked / checkCustomerFraud

6. Database

Store Object Key fields Purpose
MySQL wallet_rules redemption_limit_value, redemption_limit_period, points_earning_limit_value, points_earning_limit_period (period: hour/day/week) velocity config
MySQL wallet_history wallet_id, parentBusinessId, txn_type (1/2), event_date/event_hour/event_month velocity counting source
MongoDB fraudulent_customers contact, name, fraud_type ("Redemption Frequency Limit"/"Points Earning Limit"), parent_business_id, child_business_id, block_reason, blocked_by (System/user), status (1=pending,2=resolved), comment, resolved_comment, submitted_by, created_at, updated_at flagged customers
MongoDB blocklist_contacts parent_business_id, contact_number, source (manual/csv), timestamps hard blocklist
MongoDB fraud_alert_settings parent_business_id, alert_recipients[], channel_type (sms/whatsapp), template, sender_id, template_type (UTILITY/MARKETING) alerting config
MongoDB customers is_blocked block enforcement flag

Relationships: fraudulent_customers.contact / blocklist_contacts.contact_number = 10-digit mobileNocustomers. Velocity counts join wallet_history by wallet_id + parentBusinessId.

Indexes: ⚠️ wallet_history(wallet_id, parentBusinessId, txn_type, event_date) critical — velocity check runs on every earn/redeem. blocklist_contacts(parent_business_id, contact_number) for O(1) block lookup.

Common query (velocity — ✅ checkCustomerFraud, simplified):

SELECT COUNT(*) FROM wallet_history
WHERE wallet_id=? AND parentBusinessId=? AND txn_type=2   -- redemptions
  AND event_date >= :period_start;                        -- hour/day/week window
-- breach if COUNT >= redemption_limit_value


7. APIs

routes/prism_routes/fraud_rules_routes.js — all authMiddleware except CSV upload (file middleware). Verify.

Method Path Handler
POST /configure_fraud_wallet_rules configureFraudWalletRules
POST /configure_fraud_alert_settings configureFraudAlertSettings
POST /get_fraud_configuration getFraudConfiguration
POST (multipart) /upload_blocklist_contacts addBlocklistContacts
POST /delete_blocklist_contacts deleteBlocklistContacts
POST /block_unblock_resolve_contacts blockUnblockAndResolveContacts
POST /mark_resolve_contacts markAndResolveContacts
POST /get_blocklist_contacts listBlocklistContacts
GET /fraud/check-customer-blocked?contact= getCustomerBlocked
POST /api/v1/fraud/evaluate-customer evaluateCustomerFraud
POST /send_fraud_alert sendAlertForFraudCustomer
POST /view_fraud_customers viewFraudulentCustomer
POST /group_list_fraud_contacts groupListFraudContacts
POST /get_fraud_list_mis fraudListCustomersMis

Example — configure_fraud_wallet_rules (verify):

// request
{ "parentBusinessId": 123,
  "redemption_limit_value": 3, "redemption_limit_period": "day",
  "points_earning_limit_value": 5, "points_earning_limit_period": "day" }
// response
{ "status": true, "message": "Fraud wallet rules configured" }
CSV upload validation: column named Phone; each value 10 digits, not starting 0–5; processed in 1000-row batches; source="csv". Failures: invalid phone rows skipped/reported; alert send skipped if wallet_balance < channel_cost.


8. Code Walkthrough

checkCustomerFraud(connection, wallet_rules, wallet_id, parent_business_id, child_business_id, mobileNo, type)
  ├─ type "redemption": COUNT wallet_history txn_type=2 in redemption_limit_period vs redemption_limit_value
  ├─ type "allocation": COUNT wallet_history txn_type=1 in points_earning_limit_period vs points_earning_limit_value
  └─ breach → insert fraudulent_customers {fraud_type, block_reason, blocked_by:"System", status:1}
     returns { status_code, status, fraud_detected, action_taken }

checkCustomerIsBlocked(parent_business_id, mobileNo, customer_details)
  → lookup blocklist_contacts / customers.is_blocked → null if blocked, else { status: 1 }

addBlocklistContacts (CSV or manual)
  → validate phones (10 digits, not 0-5 start) → insert blocklist_contacts in 1000 batches (source csv/manual)

blockUnblockAndResolveContacts (status 2 block / 3 unblock)
  → set/unset customers.is_blocked ; fraudulent_customers.status=2 (+comment, submitted_by)

sendAlert(parent_business_id, db, contacts)
  → wallet_balance check → deduct (findOneAndUpdate $inc) → sms_queue_* / whatsapp_queue_* + wallet_transactions

Callers (enforcement points): loyaltyController (allocation/redemption), prismIntegration (configureLoyaltyPoints, purchaseRewards, purchaseLoyaltyCards), posistLoyaltyIntegration, handleWalletPointsFlow. Dependencies: MySQL wallet_rules/wallet_history, MongoDB fraudulent_customers/blocklist_contacts/fraud_alert_settings/customers/wallet_transactions, SMS/WA queues, CSV parser.


9. Business Rules

Rule Value Validation
Redemption velocity redemption_limit_value per redemption_limit_period (hour/day/week)
Earning velocity points_earning_limit_value per points_earning_limit_period
Auto-flag breach → fraudulent_customers blocked_by="System", status=1
Hard block blocklist_contacts / customers.is_blocked denies loyalty actions
CSV rules 10-digit, not starting 0–5; batch 1000; source="csv"
Block/unblock status 2 block / 3 unblock; both resolve flag
Alert channels SMS or WhatsApp; debits business wallet; skipped if insufficient

Feature flags/permissions: all config = Admin (authMiddleware); enforcement is automatic on every loyalty action. Hidden logic (💻/⚠️): checkCustomerFraud inserts a flag as a side effect during a normal loyalty transaction — a read that writes. Blocklist landline filter (reject phones starting 0–5) can drop valid mobiles in rare numbering edge cases. GET /fraud/check-customer-blocked is a GET (query param) while everything else is POST — auth/exposure worth reviewing (A2.2).

10. Performance

  • checkCustomerFraud runs a wallet_history COUNT on every earn/redeem ⚠️ — without (wallet_id, txn_type, event_date) index this is a hot scan; a top loyalty performance concern.
  • Blocklist lookup per action needs (parent_business_id, contact_number) index.
  • CSV upload batches 1000 — fine; alerts go through the async SMS/WA queues (non-blocking).

11. Logging

  • Auto-flags are self-documenting rows in fraudulent_customers (fraud_type, block_reason, blocked_by, created_at).
  • Block/unblock/resolve capture submitted_by, comment, resolved_comment — full admin audit.
  • Alert sends logged in wallet_transactions (debit) + the channel queues.

12. Monitoring

  • Pending flags: db.fraudulent_customers.count({ status: 1 }) (unreviewed).
  • Auto vs manual: group by blocked_by.
  • Blocklist size: db.blocklist_contacts.count({ parent_business_id: pid }).
  • Velocity hotspots: businesses with many fraud_type="Points Earning Limit" flags = possible farming attack.
  • Revocation cron: cron_statuses {cron_data:"pos_redemption_revoke"} for posRedemptionRevokeCron.

13. Troubleshooting

Issue Cause Fix Commands
Legit customer can't earn/redeem on blocklist_contacts or auto-flagged verify + unblock (status 3) db.fraudulent_customers.find({contact:"98..."})
Fraud not caught limits unset or period wrong set *_limit_value/_period SELECT redemption_limit_value,redemption_limit_period FROM wallet_rules WHERE parentBusinessId=?;
Alerts not sent insufficient wallet or no recipients/config recharge; set fraud_alert_settings db.fraud_alert_settings.findOne({parent_business_id:pid})
CSV rows dropped phone !=10 digits or starts 0–5 fix source file grep -n "10\|Phone" controllers/fraudRulesController.js
Loyalty slow under load checkCustomerFraud COUNT scan add wallet_history composite index see §10
Points not restored after POS cancel posRedemptionRevokeCron not run / not stale yet wait 5-min cron; check pending_redemptions.expires_at grep -n "*/5" crons/posRedemptionRevokeCron.js

14. FAQs

Q: Difference between checkCustomerIsBlocked and checkCustomerFraud? The former is a hard block lookup (blocklist/is_blocked); the latter is a velocity check that flags on breach.

Q: Does a velocity check block immediately? It flags into fraudulent_customers (status=1) and can trigger an alert; an admin then decides to hard-block (status=2).

Q: What periods are supported? hour / day / week.

Q: Do fraud alerts cost money? Yes — SMS/WhatsApp sends debit the business wallet.

Q: Where is this heading? Merged into loyalty as "Loyalty & Trust" (Roadmap A2.2, P1).

15. Cheat Sheet

Two gates:  checkCustomerIsBlocked (hard block) + checkCustomerFraud (velocity)
Velocity:   wallet_rules redemption/earning limit value+period (hour/day/week) ; counts wallet_history txn_type
Flag:       fraudulent_customers (fraud_type, blocked_by System, status 1)
Block:      blocklist_contacts + customers.is_blocked ; block=status2 / unblock=status3
CSV:        Phone col, 10 digits, not 0-5 start, batch 1000, source=csv
Alerts:     fraud_alert_settings → SMS/WA (debits business wallet)
Crons:      posRedemptionRevokeCron */5 (revert stale) · whatsappRefund 00:00
Perf risk:  checkCustomerFraud COUNT scans wallet_history on EVERY earn/redeem → index it
Routes:     prism_routes/fraud_rules_routes.js  (GET /fraud/check-customer-blocked; rest POST)
Roadmap:    A2.2 → Loyalty & Trust


Knowledge Tests

Level 1 — MCQs

  1. checkCustomerFraud(type="redemption") compares against… a) points_earning_limit_value b) redemption_limit_value over the period c) wallet_balance d) LTV Answer: b.

  2. A velocity breach results in… a) instant hard block b) a fraudulent_customers flag (status=1, blocked_by=System) c) order cancellation d) a refund Answer: b. Admin then decides to block.

  3. CSV blocklist upload rejects phone numbers that… a) contain letters only b) are 10 digits c) start with 0–5 or aren't 10 digits d) are duplicates Answer: c. Landline/invalid filter.

  4. To hard-block a customer, an admin sets status… a) 1 b) 2 (customers.is_blocked=1) c) 3 d) 0 Answer: b. status 3 unblocks.

  5. Fraud Prevention's roadmap destiny is… a) deletion b) merge into "Loyalty & Trust" (A2.2) c) move to Kafka d) frontend-only Answer: b.

Level 2 — Scenarios

  1. A brand reports customers "farming" welcome/earning points via repeat fake orders. Expected: set points_earning_limit_value/period in wallet_rules; checkCustomerFraud("allocation") will flag offenders into fraudulent_customers; enable alerts; admin hard-blocks repeat offenders; consider CSV bulk blocklist for known numbers.

  2. After enabling tight limits, legit high-frequency customers get flagged. Expected: the velocity threshold/period is too strict; raise *_limit_value or widen _period; unblock falsely-flagged (status 3); note the read-that-writes side effect creates flags during normal txns.

Level 3 — Code Reading

checkCustomerFraud counts wallet_history rows by txn_type within a period and, on breach, inserts into fraudulent_customers. Question: why is calling this on the hot loyalty path both a correctness convenience and a performance/observability risk? Expected: convenient (enforcement + flagging in one call, at the exact transaction) but it's a read that writes on every earn/redeem — needs a wallet_history composite index, and side-effect writes make the function non-idempotent under retries (double flags).

Level 4 — Architecture

Fraud checks run synchronously inside every loyalty allocation/redemption. Roadmap A2.2 merges Fraud into "Loyalty & Trust". Propose how to keep enforcement authoritative while removing the per-transaction COUNT scan. Expected: maintain rolling counters (Redis/materialized per customer-period) updated on each txn instead of COUNT-scanning wallet_history; async flagging via event; idempotent flag writes; keep hard-block lookup O(1) with an indexed blocklist cache.

Level 5 — Production Debugging

Ticket: "Loyalty API latency spiked 5× for one large brand after they enabled fraud rules." Expected reasoning: checkCustomerFraud now runs a wallet_history COUNT on every earn/redeem; that brand has huge wallet_history and no (wallet_id, txn_type, event_date) index → full scans; MySQL jobsPool/webPool saturates. Fix: add the composite index (or counter cache), verify EXPLAIN, watch pool stats; the latency is the fraud scan, not the loyalty math.


Practical Assignments

  1. Index the hot path. Capture the checkCustomerFraud COUNT query, EXPLAIN it against a large wallet_history, add the composite index, and measure before/after latency.
  2. Blocklist round-trip. Bulk-upload a CSV of 3 numbers, confirm blocklist_contacts + validation rejects a landline, then verify a loyalty earn is denied for a blocked number and allowed after unblock (status 3).
  3. Fraud MIS dashboard. Using fraudListCustomersMis/groupListFraudContacts, build a report of flags per fraud_type per parent business over 30 days and identify the top farming targets; cross-link findings to Loyalty and Referral.