Wallet Validation & Fraud Rules

Comprehensive guide to input validation, business validation, and fraud/risk controls in Three-D Wallet.

Production Ready Version 1.0.0

Overview

This document defines the validation and fraud detection rules enforced by the Wallet API during wallet creation, funding, transfers, withdrawals, refunds, and dispute flows. Rules are grouped into hard failures (block the request) and soft flags (allow but log/alert).

Hard Validation

Request is rejected with error. Examples: invalid tokens, duplicate transaction, insufficient balance.

Hard

Soft Validation

Request may proceed but is flagged and logged for review/alerting. Examples: unusual velocity, geo mismatch.

Soft

Validation Rules

1) Authentication & Access

  • Client Token (Hard): Required X-Client-Token must be valid and active.
  • Handshake Token (Hard): Short-lived session token must be present and not expired.
  • IP Whitelist (Hard): Request IP/CIDR must be in client's whitelist.
  • Rate Limit (Hard): Endpoint-specific per-minute limits must not be exceeded.

2) Request Schema & Formats

  • Required Fields (Hard): payable_id, type, amount, currency, transaction_ref, entry_type.
  • Types & Ranges (Hard): amount numeric (> 0), currency in allowed set (e.g., PKR, USD), type in {IN, OUT}.
  • Idempotency Key (Hard for writes): transaction_ref required and unique per (payable_id, type).
  • Signature/Hash (Hard if enabled): Optional request signature must verify.

3) Wallet State & Business Rules

  • Status (Hard): Wallet must be active; inactive/defaulter blocked.
  • Balance Check (Hard for OUT): Available balance must cover amount + fees.
  • Locking (Hard): Per-wallet lock must be acquired before mutating balance.
  • Currency Match (Hard): Wallet currency must match request currency (or be convertible as per config).
  • Transaction Limits (Hard): Per-txn, daily, and monthly caps by client/tier.
  • Duplicate Rule (Hard): Unique on (payable_id, type, transaction_ref).

4) Data Integrity & Ledger

  • Stored Balance Reconciliation (Soft -> Hard if drift exceeds threshold): Stored balance must equal sum of successful transactions.
  • No Negative Balance (Hard unless dispute): Non-dispute wallets cannot be negative.
  • Audit Trail (Hard): All state changes must be logged with hashes.

5) API Duplicate Semantics

  • Same transaction_ref can be reused across opposite types (e.g., booking OUT and refund IN).
  • Same transaction_ref cannot be reused for the same type on the same wallet.
  • Enforced by DB unique index and pre-insert service checks.

Fraud Detection Rules

Velocity & Pattern (Soft → Hard)

  • ≥ 6 consecutive same-type successful txns with ≤ 15s gaps in last 24h → mark suspicious; auto-disable if threshold breached N times/day.
  • Rapid-fire retries with small amount variance.
  • Unusual hours spike vs historical baseline.

Geo/IP/Device (Soft)

  • IP country changes between consecutive requests.
  • Device fingerprint mismatch vs last trusted device.
  • Requests from high-risk regions (configurable list).

Abuse Signals (Soft → Hard)

  • Excessive failed auth or OTP attempts.
  • Refund/chargeback loops using same references.
  • Multi-account linkage with shared device/IP.

Blacklist/Watchlist (Hard)

  • Blocked payable_id, device_id, IP, or token.
  • Known compromised tokens or leaked credentials.

Fraud Review Flow

graph TD START[Txn Accepted] --> CHECK{Fraud Rules} CHECK -->|Soft Flag| LOG[Log + Alert] CHECK -->|Hard Fail| BLOCK[Block Request] LOG --> PROCEED[Proceed with Caution] BLOCK --> DISABLE[Disable Wallet if Needed] DISABLE --> REVIEW[Manual Review]

Simple Examples

Duplicate Transaction (Hard)

{
  "payable_id": 1001,
  "type": "OUT",
  "amount": 500,
  "currency": "PKR",
  "transaction_ref": "ORDER-123",
  "entry_type": "booking"
}
// Second request with same (payable_id, type, transaction_ref)
// → 409 Conflict: "Duplicate transaction_ref for this type"

Velocity Spike (Soft)

Time window: 10 minutes
Events: 8 OUT txns of 200 PKR each
Gaps: 5-10 seconds between txns
Action: Allow, flag with risk_score=0.7 and send alert

Insufficient Balance (Hard)

Wallet balance: 1,000 PKR
Request: OUT 1,050 PKR (including fee)
Action: 422 Unprocessable Entity: "Insufficient balance"

Balance Mismatch (Soft → Hard)

Stored balance: 10,000 PKR
Sum(success txns): 9,500 PKR
Drift: 500 PKR (> threshold 100)
Action: Auto-inactive + alert (if configured)

Enforcement & System Actions

  • Block: Return 4xx with descriptive error, do not mutate state.
  • Soft-Flag: Allow but log, increment risk counters, and send alert.
  • Auto-Disable: Set wallet status to inactive when critical thresholds breached.
  • Audit: Write immutable audit logs with cryptographic hashes.

Database Constraints

// Unique index (MySQL) preventing duplicates
ALTER TABLE wallet_transactions
ADD UNIQUE KEY uniq_wallet_type_ref (payable_id, type, transaction_ref);

// Helpful indexes
CREATE INDEX idx_wallet_created_at ON wallet_transactions (payable_id, created_at);
CREATE INDEX idx_type_created_at ON wallet_transactions (type, created_at);

Validation Pseudocode

function processTransaction(req) {
  assertValidTokens(req.headers);      // Hard
  assertIpWhitelisted(req.ip);         // Hard
  validateSchema(req.body);            // Hard
  enforceDuplicateRule(req.body);      // Hard
  withWalletLock(req.payable_id, () => {
    enforceLimits(req.body);           // Hard
    assertSufficientBalance(req.body); // Hard
    recordTransaction();
  });
  riskScore = evaluateFraudSignals(req); // Soft → Hard by threshold
  if (riskScore > 0.9) autoDisableWallet();
  return success();
}