Skip to content

Eneza - Product Requirements Document

Africa's First WhatsApp Status Advertising Platform

Connecting brands with millions of engaged WhatsApp users across Africa

Status: 🟢 LIVE (Production) Version: 2.0 Users: 12,000+ Live URL: https://api.eneza.app / https://eneza.appLast Updated: March 2026


Table of Contents

  1. Vision & Problem Statement
  2. Solution Overview
  3. Core Features
  4. User Journeys
  5. Data Models
  6. API Reference
  7. Service Architecture
  8. Authentication & Authorization
  9. Billing System
  10. Verification System
  11. Notifications
  12. Analytics & Reporting
  13. Technical Stack
  14. Roadmap
  15. Gaps & Needed Improvements

1. Vision & Problem Statement

The Problem

  1. For Advertisers: Traditional digital advertising in Africa is:

    • Expensive (CPM rates don't match African spending power)
    • Low trust (banner blindness, ad fatigue)
    • Poor targeting (users aren't the engaged audience)
    • Difficult to verify (impressions ≠ actual views)
  2. For African Youth:

    • High unemployment rates (40%+ in many African countries)
    • WhatsApp is ubiquitous (700M+ users in Africa)
    • No easy way to monetize their social influence
    • Traditional gig economy platforms exclude Africa

The Vision

Eneza ("spread" in Swahili) creates Africa's first peer-to-peer WhatsApp status advertising network where:

  • Advertisers pay for verified, real human views on WhatsApp statuses
  • Posters/Publishers earn money by posting ads to their WhatsApp status for 24 hours
  • AI-powered verification ensures authenticity and prevents fraud

Think Instagram Influencer Marketing meets Gig Economy — but designed for Africa's most popular app: WhatsApp.

Business Model

┌─────────────────────┐         ┌─────────────────────┐
│    ADVERTISERS      │         │   POSTERS (Users)   │
│  (Brands/Agencies)  │         │  (WhatsApp Users)   │
└─────────┬───────────┘         └───────────┬─────────┘
          │                                 │
          │ Pay per verified view           │ Earn per verified view
          │ ($0.01 - $0.05/view)            │ (60-70% of ad revenue)
          │                                 │
          └─────────────┬───────────────────┘

              ┌─────────▼─────────┐
              │      ENEZA       │
              │   (Platform Fee   │
              │    30-40%)        │
              └───────────────────┘

2. Solution Overview

How It Works

┌──────────────────────────────────────────────────────────────────────────────────┐
│                           ENEZA WORKFLOW                                         │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  1. ADVERTISER                    2. POSTER                    3. VERIFICATION  │
│  ═══════════                      ═══════                      ══════════════   │
│                                                                                  │
│  ┌─────────────┐                  ┌─────────────┐              ┌─────────────┐  │
│  │ Create Ad   │                  │ Subscribe   │              │ AI Verifies │  │
│  │ Campaign    │──────────────────▶│ to Campaign │──────────────▶│ Screenshot  │  │
│  │ + Video     │                  │             │              │             │  │
│  └─────────────┘                  └─────────────┘              └─────────────┘  │
│         │                                │                            │          │
│         │                                │                            │          │
│  ┌─────────────┐                  ┌─────────────┐              ┌─────────────┐  │
│  │ Fund with   │                  │ Download    │              │ Screenshot  │  │
│  │ Credits     │                  │ Personalized│              │ Passes AI   │  │
│  │ (Stripe)    │                  │ Ad Video    │              │ Checks?     │  │
│  └─────────────┘                  └─────────────┘              └─────────────┘  │
│                                          │                            │          │
│                                          │                            │          │
│                                   ┌─────────────┐              ┌─────────────┐  │
│                                   │ Post to     │              │ View Count  │  │
│                                   │ WhatsApp    │              │ Extracted   │  │
│                                   │ Status      │              │ by Gemini   │  │
│                                   └─────────────┘              └─────────────┘  │
│                                          │                            │          │
│                                          │                            │          │
│                                   ┌─────────────┐              ┌─────────────┐  │
│                                   │ Wait 20-24  │              │ Payout      │  │
│                                   │ Hours       │──────────────▶│ Calculated  │  │
│                                   │             │              │ & Queued    │  │
│                                   └─────────────┘              └─────────────┘  │
│                                          │                            │          │
│                                          │                            │          │
│                                   ┌─────────────┐              ┌─────────────┐  │
│                                   │ Upload      │              │ Mobile      │  │
│                                   │ Screenshot  │              │ Money       │  │
│                                   │ of Views    │              │ Withdrawal  │  │
│                                   └─────────────┘              └─────────────┘  │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

Key Differentiators

FeatureTraditional AdsEneza
View VerificationSelf-reportedAI-verified screenshots
Audience TrustLow (strangers)High (friends/contacts)
Fraud DetectionBasicML-powered multi-check pipeline
PersonalizationGenericQR-coded watermarked videos
PayoutN/AReal-time mobile money
Geographic FocusGlobalAfrica-first

3. Core Features

3.1 For Advertisers

Campaign Management

  • Create Ad Campaigns with video/image content (up to 200MB)
  • Set Budget in USD with guaranteed view counts
  • Target Countries and demographics
  • Multiple Video Variants (portrait/landscape, different resolutions)
  • Campaign Scheduling with start/end dates
  • Real-time Analytics dashboard

Billing & Deposits

  • Stripe Integration for credit card payments
  • Credit System (USD-based ad credits)
  • Volume Discounts on large deposits
  • Invoice Generation for accounting
  • Deposit History and transaction tracking

Analytics

  • View Counts (total, verified, pending)
  • Geographic Distribution of views
  • Subscription Rates by campaign
  • Cost per Verified View calculations
  • Fraud Detection Metrics

3.2 For Posters (Publishers)

Ad Discovery & Subscription

  • Browse Available Ads with earnings potential
  • Subscribe to Campaigns (one at a time, max based on tier)
  • Download Personalized Video with unique QR watermark
  • 24-Hour Posting Window with automatic expiry

Screenshot Verification

  • Upload Screenshot of WhatsApp status views
  • AI-Powered Verification (Gemini Vision)
  • Multi-Check Pipeline (QR code, image matching, view count extraction)
  • Real-time Verification Status updates

Earnings & Payouts

  • Per-View Earnings (varies by campaign, country, tier)
  • Wallet Balance tracking in USD + local currency
  • Mobile Money Withdrawals (MTN MoMo, Airtel Money, etc.)
  • Transaction History with status tracking
  • Minimum Withdrawal thresholds by country

3.3 Growth Engine (Gamification)

Referral System

  • Unique Referral Codes for each user
  • Referral Bonuses ($0.25-$0.50 per successful referral)
  • Referral Tracking with friend activity feed

Tier System

TierReferralsEarnings Multiplier
Bronze0-41.0x
Silver5-141.1x (+10%)
Gold15-291.2x (+20%)
Platinum30-491.3x (+30%)
Diamond50+1.5x (+50%)

Achievement System

  • First Referral (Bonus: $0.50)
  • Social Butterfly (5 referrals, $1.00)
  • Influencer (10 referrals, $2.50)
  • Community Builder (25 referrals, $5.00)
  • Ambassador (50 referrals, $10.00)
  • Legend (100 referrals, $25.00)

Streak System

  • Daily Activity Tracking
  • Streak Multipliers (+5% to +25% based on streak length)
  • Streak Shields (protects against missed days)
  • Milestone Achievements (7, 14, 30, 60, 90, 180 days)

3.4 Admin Dashboard

  • User Management (ban/unban, fraud review)
  • Campaign Moderation (approve/reject ads)
  • Transaction Oversight (approve withdrawals)
  • Analytics Dashboard (platform-wide metrics)
  • Fraud Detection Reports
  • Support Ticket Management

4. User Journeys

4.1 Advertiser Journey

┌────────────────────────────────────────────────────────────────────────────────┐
│                        ADVERTISER JOURNEY                                      │
├────────────────────────────────────────────────────────────────────────────────┤
│                                                                                │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Sign Up  │────▶│  Verify   │────▶│  Deposit  │────▶│  Create   │        │
│   │  Email    │     │  Email    │     │  Credits  │     │  Campaign │        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│                                                                │               │
│                                                                ▼               │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Review   │◀────│  Monitor  │◀────│  Campaign │◀────│  Upload   │        │
│   │  Results  │     │  Live     │     │  Goes     │     │  Video    │        │
│   │           │     │  Stats    │     │  Live     │     │           │        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│                                                                                │
│   Key Metrics:                                                                 │
│   - Views: Total | Verified | Pending                                          │
│   - Subscriptions: Active | Completed | Expired                                │
│   - Spend: Total USD | Cost per View                                           │
│   - Geographic: Views by Country                                               │
│                                                                                │
└────────────────────────────────────────────────────────────────────────────────┘

Key Touchpoints

  1. Registration: Email + password + company details
  2. Email Verification: Required before depositing
  3. Stripe Checkout: Secure payment with Stripe
  4. Campaign Creation: Upload video, set budget, choose countries
  5. Campaign Activation: Automatic when budget is funded
  6. Monitoring: Real-time dashboard with live stats
  7. Invoices: PDF invoices for accounting

4.2 Poster (Publisher) Journey

┌────────────────────────────────────────────────────────────────────────────────┐
│                          POSTER JOURNEY                                        │
├────────────────────────────────────────────────────────────────────────────────┤
│                                                                                │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Sign Up  │────▶│  Verify   │────▶│  Browse   │────▶│ Subscribe │        │
│   │  (Phone)  │     │  OTP      │     │  Ads      │     │  to Ad    │        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│                                                                │               │
│                                                                ▼               │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Upload   │────▶│  Wait for │────▶│  Post to  │◀────│  Download │        │
│   │ Screenshot│     │ Verifi-   │     │  WhatsApp │     │ Personal- │        │
│   │           │     │ cation    │     │  Status   │     │ ized Video│        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│        │                                                                       │
│        ▼                                                                       │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐                           │
│   │  View     │────▶│  Request  │────▶│  Receive  │                           │
│   │  Earnings │     │ Withdrawal│     │  Mobile   │                           │
│   │           │     │           │     │  Money    │                           │
│   └───────────┘     └───────────┘     └───────────┘                           │
│                                                                                │
│   Earnings Formula:                                                            │
│   Base Rate × View Count × Tier Multiplier × Streak Multiplier                 │
│                                                                                │
│   Example: $0.002/view × 50 views × 1.2 (Gold) × 1.1 (Streak) = $0.132         │
│                                                                                │
└────────────────────────────────────────────────────────────────────────────────┘

Key Touchpoints

  1. Registration: Phone number + OTP verification
  2. Country Selection: Determines available campaigns and payout methods
  3. Profile Setup: Date of birth validation
  4. Ad Browsing: Filter by category, earnings potential
  5. Subscription: Get unique subscription code
  6. Video Download: Personalized with QR watermark
  7. WhatsApp Posting: User posts to their status
  8. Wait Period: 20-24 hours minimum
  9. Screenshot Upload: Must show view count
  10. Verification: AI pipeline processes screenshot
  11. Payout: View count × rate = earnings
  12. Withdrawal: Request mobile money payout

4.3 Admin Journey

┌────────────────────────────────────────────────────────────────────────────────┐
│                           ADMIN JOURNEY                                        │
├────────────────────────────────────────────────────────────────────────────────┤
│                                                                                │
│   ┌───────────────────────────────────────────────────────────────────────┐   │
│   │                      DAILY OPERATIONS                                  │   │
│   └───────────────────────────────────────────────────────────────────────┘   │
│                                                                                │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Review   │     │  Approve  │     │  Monitor  │     │  Handle   │        │
│   │  Fraud    │     │  With-    │     │  Platform │     │  Support  │        │
│   │  Alerts   │     │  drawals  │     │  Health   │     │  Tickets  │        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│                                                                                │
│   ┌───────────────────────────────────────────────────────────────────────┐   │
│   │                      MODERATION                                        │   │
│   └───────────────────────────────────────────────────────────────────────┘   │
│                                                                                │
│   - Review flagged screenshots (NEEDS_REVIEW status)                           │
│   - Manually approve/reject verification results                               │
│   - Ban users with repeated fraud attempts                                     │
│   - Moderate ad content before going live                                      │
│                                                                                │
└────────────────────────────────────────────────────────────────────────────────┘

5. Data Models

5.1 Core Tables

User (Posters/Publishers)

prisma
model User {
  id                 String              @id @default(uuid())
  phoneNumber        String              @unique
  dob                DateTime?
  countryId          String?
  paymentProcessorId String?
  accountNumber      String?
  referralCode       String?             @unique
  referredBy         String?             // ID of referring user
  
  // Verification
  isVerified         Boolean             @default(false)
  isPremium          Boolean             @default(false)
  isBlocked          Boolean             @default(false)
  blockReason        String?
  
  // Push Notifications
  fcmToken           String?
  
  // Stats (denormalized for performance)
  totalEarnings      Decimal             @default(0)
  availableBalance   Decimal             @default(0)
  pendingBalance     Decimal             @default(0)
  
  // Growth Engine
  referralTierId     String?
  
  // Timestamps
  createdAt          DateTime            @default(now())
  updatedAt          DateTime            @updatedAt
  
  // Relations
  country            Country?            @relation(...)
  paymentProcessor   PaymentProcessor?   @relation(...)
  subscriptions      Subscription[]
  transactions       Transaction[]
  screenshots        Screenshot[]
  userStats          UserStats?
  userStreak         UserStreak?
  userAchievements   UserAchievement[]
  referralTier       ReferralTier?       @relation(...)
  referredUsers      User[]              @relation("UserReferrals")
  referrer           User?               @relation("UserReferrals")
}

Advertiser

prisma
model Advertiser {
  id               String          @id @default(uuid())
  email            String          @unique
  password         String
  contactPerson    String
  companyName      String?
  businessAddress  String?
  industry         String?
  
  // Verification
  isVerified       Boolean         @default(false)
  emailVerified    Boolean         @default(false)
  status           AdvertiserStatus @default(PENDING)
  
  // Billing (Stripe)
  stripeCustomerId String?
  
  // Credits & Balance
  credits          Decimal         @default(0) // USD credits
  totalSpent       Decimal         @default(0)
  
  // Timestamps
  createdAt        DateTime        @default(now())
  updatedAt        DateTime        @updatedAt
  
  // Relations
  ads              Ad[]
  deposits         Deposit[]
  invoices         Invoice[]
  supportTickets   SupportTicket[]
}

enum AdvertiserStatus {
  PENDING
  ACTIVE
  SUSPENDED
  BANNED
}

Ad (Campaign)

prisma
model Ad {
  id                 String         @id @default(uuid())
  advertiserId       String
  title              String
  description        String?
  categoryId         String?
  shortCode          String         @unique // e.g., "AD-ABC123"
  
  // Media
  video              String?        // Primary video URL
  videoVariants      Json?          // Multiple resolution variants
  adThumbnail        String?
  adImage            String?
  
  // Budget & Pricing
  budgetUsd          Decimal        @default(0)
  guaranteedViews    Int            @default(0)
  costPerViewUsd     Decimal?       // Calculated
  
  // Country Targeting
  targetCountries    Json?          // Array of country IDs
  
  // Status & Scheduling
  active             Boolean        @default(false)
  status             AdStatus       @default(DRAFT)
  startDate          DateTime?
  endDate            DateTime?
  
  // Timestamps
  createdAt          DateTime       @default(now())
  updatedAt          DateTime       @updatedAt
  
  // Relations
  advertiser         Advertiser     @relation(...)
  category           Category?      @relation(...)
  subscriptions      Subscription[]
  adStats            AdStats?
  adPricing          AdPricing[]
}

enum AdStatus {
  DRAFT
  PENDING_APPROVAL
  ACTIVE
  PAUSED
  COMPLETED
  CANCELLED
  REJECTED
}

Subscription (User's subscription to an ad)

prisma
model Subscription {
  id              String              @id @default(uuid())
  userId          String
  adId            String
  code            String              @unique // Unique subscription code for QR
  
  // Status
  isActive        Boolean             @default(true)
  status          SubscriptionStatus  @default(ACTIVE)
  
  // Device Info (for fraud detection)
  phoneInfo       Json?
  ipAddress       String?
  ipCountry       String?
  
  // Personalized Video
  personalizedVideoUrl String?
  personalizedVideoStatus VideoProcessingStatus?
  
  // Timestamps
  subscribeTime   DateTime            @default(now())
  downloadedAt    DateTime?
  expiresAt       DateTime?
  completedAt     DateTime?
  createdAt       DateTime            @default(now())
  updatedAt       DateTime            @updatedAt
  
  // Relations
  user            User                @relation(...)
  ad              Ad                  @relation(...)
  screenshots     Screenshot[]
}

enum SubscriptionStatus {
  ACTIVE
  COMPLETED
  EXPIRED
  CANCELLED
  REJECTED
  FAILED
}

enum VideoProcessingStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
}

Screenshot (Verification submission)

prisma
model Screenshot {
  id                  String              @id @default(uuid())
  userId              String
  subscriptionId      String
  adId                String
  
  // Image Data
  screenshotLink      String?             // R2/S3 URL
  processedImageUrl   String?             // Processed version
  
  // User-submitted data
  submittedViews      Int?
  
  // Verification Results
  status              ScreenshotStatus    @default(PENDING)
  verificationScore   Decimal?            // 0.0 - 1.0
  verificationResult  Json?               // Full verification details
  extractedViews      Int?                // AI-extracted view count
  
  // Payout
  payoutAmount        Decimal?
  payoutCurrency      String?
  payoutStatus        PayoutStatus?
  
  // Fraud Detection
  fraudScore          Decimal?
  fraudFlags          Json?
  isManualReview      Boolean             @default(false)
  
  // Timestamps
  submissionTime      DateTime            @default(now())
  verifiedAt          DateTime?
  processedAt         DateTime?
  createdAt           DateTime            @default(now())
  updatedAt           DateTime            @updatedAt
  
  // Relations
  user                User                @relation(...)
  subscription        Subscription        @relation(...)
  ad                  Ad                  @relation(...)
}

enum ScreenshotStatus {
  PENDING
  PROCESSING
  VERIFIED
  REJECTED
  NEEDS_REVIEW
  FAILED
  FRAUD
}

enum PayoutStatus {
  PENDING
  QUEUED
  PROCESSING
  COMPLETED
  FAILED
}

5.2 Financial Tables

Transaction

prisma
model Transaction {
  id                    String            @id @default(uuid())
  userId                String
  type                  TransactionType
  status                TransactionStatus @default(PENDING)
  
  // Amounts
  amountUsd             Decimal
  amountLocal           Decimal?
  currencyCode          String            @default("USD")
  exchangeRate          Decimal?
  
  // Payment Details
  paymentProcessorId    String?
  paymentProcessorRef   String?           // External reference
  accountNumber         String?
  phoneNumber           String?
  
  // References
  screenshotId          String?
  subscriptionId        String?
  adId                  String?
  
  // Metadata
  description           String?
  notes                 String?
  failureReason         String?
  
  // Timestamps
  processedAt           DateTime?
  createdAt             DateTime          @default(now())
  updatedAt             DateTime          @updatedAt
  
  // Relations
  user                  User              @relation(...)
  paymentProcessor      PaymentProcessor? @relation(...)
  screenshot            Screenshot?       @relation(...)
}

enum TransactionType {
  EARNING           // From verified screenshots
  WITHDRAWAL        // Cash out to mobile money
  REFERRAL_BONUS    // From referring users
  ACHIEVEMENT_BONUS // From achievements
  ADJUSTMENT        // Manual adjustments
  REFUND
}

enum TransactionStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
  CANCELLED
  ON_HOLD
}

Deposit (Advertiser deposits)

prisma
model Deposit {
  id                 String          @id @default(uuid())
  depositNumber      String          @unique // e.g., "DEP-2026-0001"
  advertiserId       String
  
  // Amounts
  amountUsd          Decimal
  bonusPercent       Int?            // Volume discount percentage
  bonusAmount        Decimal?
  totalCredits       Decimal         // amountUsd + bonusAmount
  
  // Stripe
  stripePaymentIntentId  String?
  stripeCheckoutSessionId String?
  
  // Status
  status             DepositStatus   @default(PENDING)
  
  // Metadata
  description        String?
  
  // Timestamps
  completedAt        DateTime?
  createdAt          DateTime        @default(now())
  updatedAt          DateTime        @updatedAt
  
  // Relations
  advertiser         Advertiser      @relation(...)
}

enum DepositStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
  REFUNDED
}

Invoice

prisma
model Invoice {
  id                String         @id @default(uuid())
  invoiceNumber     String         @unique // e.g., "INV-2026-0001"
  advertiserId      String
  depositId         String?
  
  // Amounts
  subtotalUsd       Decimal
  taxUsd            Decimal        @default(0)
  totalUsd          Decimal
  
  // Status
  status            InvoiceStatus  @default(DRAFT)
  
  // PDF
  pdfUrl            String?
  
  // Dates
  issueDate         DateTime       @default(now())
  dueDate           DateTime?
  paidAt            DateTime?
  
  // Timestamps
  createdAt         DateTime       @default(now())
  updatedAt         DateTime       @updatedAt
  
  // Relations
  advertiser        Advertiser     @relation(...)
  deposit           Deposit?       @relation(...)
  items             InvoiceItem[]
}

enum InvoiceStatus {
  DRAFT
  SENT
  PAID
  OVERDUE
  CANCELLED
}

5.3 Growth Engine Tables

UserStats

prisma
model UserStats {
  id                     String    @id @default(uuid())
  userId                 String    @unique
  
  // Screenshot Stats
  totalScreenshots       Int       @default(0)
  approvedScreenshots    Int       @default(0)
  rejectedScreenshots    Int       @default(0)
  pendingScreenshots     Int       @default(0)
  
  // View Stats
  totalVerifiedViews     Int       @default(0)
  
  // Referral Stats
  totalReferrals         Int       @default(0)
  completedReferrals     Int       @default(0) // Referrals who earned
  
  // Earnings Stats
  totalEarningsUsd       Decimal   @default(0)
  totalWithdrawalsUsd    Decimal   @default(0)
  totalReferralBonusUsd  Decimal   @default(0)
  
  // Leaderboard
  weeklyRank             Int?
  monthlyRank            Int?
  allTimeRank            Int?
  
  // Timestamps
  updatedAt              DateTime  @updatedAt
  
  // Relations
  user                   User      @relation(...)
}

UserStreak

prisma
model UserStreak {
  id                 String    @id @default(uuid())
  userId             String    @unique
  
  currentStreak      Int       @default(0)
  longestStreak      Int       @default(0)
  totalDaysActive    Int       @default(0)
  
  lastActivityDate   DateTime?
  streakShields      Int       @default(0) // Protect against missed days
  shieldUsedAt       DateTime?
  
  // Timestamps
  createdAt          DateTime  @default(now())
  updatedAt          DateTime  @updatedAt
  
  // Relations
  user               User      @relation(...)
}

ReferralTier

prisma
model ReferralTier {
  id                  String   @id @default(uuid())
  name                String   @unique // Bronze, Silver, Gold, Platinum, Diamond
  minReferrals        Int
  maxReferrals        Int?     // null = unlimited
  earningsMultiplier  Decimal  @default(1.0) // 1.1 = +10%
  badgeColor          String   // Hex color
  description         String?
  sortOrder           Int
  isActive            Boolean  @default(true)
  
  // Relations
  users               User[]
}

Achievement

prisma
model Achievement {
  id           String   @id @default(uuid())
  key          String   @unique // e.g., "first_referral"
  name         String
  description  String
  category     String   // referral, streak, earning, view
  requirement  Int      // Number required to unlock
  bonusUsd     Decimal  // Bonus awarded
  badgeColor   String
  iconUrl      String?
  sortOrder    Int
  isActive     Boolean  @default(true)
  
  // Relations
  userAchievements UserAchievement[]
}

UserAchievement

prisma
model UserAchievement {
  id            String      @id @default(uuid())
  userId        String
  achievementId String
  
  progress      Int         @default(0) // Current progress
  completed     Boolean     @default(false)
  completedAt   DateTime?
  bonusClaimed  Boolean     @default(false)
  claimedAt     DateTime?
  
  // Timestamps
  createdAt     DateTime    @default(now())
  updatedAt     DateTime    @updatedAt
  
  // Relations
  user          User        @relation(...)
  achievement   Achievement @relation(...)
  
  @@unique([userId, achievementId])
}

5.4 Configuration Tables

Country

prisma
model Country {
  id                  String   @id @default(uuid())
  name                String
  code                String   @unique // ISO 3166-1 alpha-2
  callingCode         String   // e.g., "+254"
  currencyCode        String   // e.g., "KES"
  currencySymbol      String   // e.g., "KSh"
  phoneNumberLength   Int?     // Expected phone number length
  minimumWithdrawal   Decimal  // Minimum withdrawal in USD
  isActive            Boolean  @default(true)
  isWaitlistOnly      Boolean  @default(false)
  
  // Relations
  users               User[]
  paymentProcessors   PaymentProcessor[]
  cities              City[]
  adPricing           AdPricing[]
}

PaymentProcessor

prisma
model PaymentProcessor {
  id           String   @id @default(uuid())
  name         String   // e.g., "MTN MoMo"
  code         String   @unique
  type         String   // MOBILE_MONEY, BANK_TRANSFER
  countryId    String
  
  isActive     Boolean  @default(true)
  
  // API Configuration (encrypted)
  apiKey       String?
  apiSecret    String?
  apiEndpoint  String?
  
  // Relations
  country      Country  @relation(...)
  users        User[]
  transactions Transaction[]
}

Category

prisma
model Category {
  id          String   @id @default(uuid())
  name        String
  description String?
  iconUrl     String?
  color       String?
  sortOrder   Int      @default(0)
  isActive    Boolean  @default(true)
  
  // Relations
  ads         Ad[]
}

5.5 Support Tables

SupportTicket

prisma
model SupportTicket {
  id           String         @id @default(uuid())
  ticketNumber String         @unique // e.g., "TKT-2026-0001"
  
  // Submitter (one of)
  userId       String?
  advertiserId String?
  
  // Content
  subject      String
  description  String
  category     String?        // billing, technical, fraud, other
  priority     Priority       @default(MEDIUM)
  status       TicketStatus   @default(OPEN)
  
  // Assignment
  assignedTo   String?        // Admin ID
  
  // Timestamps
  resolvedAt   DateTime?
  createdAt    DateTime       @default(now())
  updatedAt    DateTime       @updatedAt
  
  // Relations
  user         User?          @relation(...)
  advertiser   Advertiser?    @relation(...)
  messages     TicketMessage[]
}

enum Priority {
  LOW
  MEDIUM
  HIGH
  URGENT
}

enum TicketStatus {
  OPEN
  IN_PROGRESS
  WAITING_ON_USER
  RESOLVED
  CLOSED
}

5.6 Analytics Tables

AdStats (Denormalized for performance)

prisma
model AdStats {
  id                   String   @id @default(uuid())
  adId                 String   @unique
  
  // Subscription Counts
  subscriptionCount    Int      @default(0)
  activeSubscriptions  Int      @default(0)
  completedSubscriptions Int    @default(0)
  
  // View Counts
  totalViews           Int      @default(0)
  verifiedViews        Int      @default(0)
  balanceViews         Int      @default(0) // Views from balance (after deductions)
  estimatedExpectedViews Int    @default(0)
  
  // Screenshot Counts
  screenshotUploadCount Int     @default(0)
  screenshotApprovedCount Int   @default(0)
  screenshotRejectedCount Int   @default(0)
  
  // Download Counts
  downloadCount        Int      @default(0)
  
  // Timestamps
  updatedAt            DateTime @updatedAt
  
  // Relations
  ad                   Ad       @relation(...)
}

6. API Reference

6.1 Authentication Endpoints

User (Poster) Auth

POST /auth/signup                    # Register with phone + OTP
POST /auth/login                     # Login with phone + OTP
POST /auth/verify-otp                # Verify OTP code
POST /auth/resend-otp                # Resend OTP
POST /auth/logout                    # Logout (invalidate token)
POST /auth/refresh-token             # Refresh JWT

Advertiser Auth

POST /advertisers/signup             # Register with email
POST /advertisers/login              # Login with email + password
POST /advertisers/verify-email       # Verify email token
POST /advertisers/forgot-password    # Password reset request
POST /advertisers/reset-password     # Reset password with token

Admin Auth

POST /admin/auth/login               # Admin login
POST /admin/auth/logout              # Admin logout
GET  /admin/auth/me                  # Get current admin

6.2 Ad Endpoints

# Public
GET  /ads                            # List active ads (for posters)
GET  /ads/:id                        # Get ad details
GET  /ads/shortcode/:shortCode       # Get ad by short code

# Authenticated (Advertisers)
POST /advertisers/ads                # Create new ad
PUT  /advertisers/ads/:id            # Update ad
DELETE /advertisers/ads/:id          # Delete ad
POST /advertisers/ads/:id/upload-video   # Upload video
GET  /advertisers/ads/:id/stats      # Get ad statistics

# Admin
GET  /admin/ads                      # List all ads
PUT  /admin/ads/:id/approve          # Approve ad
PUT  /admin/ads/:id/reject           # Reject ad

6.3 Subscription Endpoints

# Authenticated (Users)
POST /subscriptions                  # Subscribe to ad
GET  /subscriptions                  # List my subscriptions
GET  /subscriptions/:id              # Get subscription details
GET  /subscriptions/:id/status       # Check subscription status
DELETE /subscriptions/:id            # Cancel subscription

# Video Download
GET  /subscriptions/:id/video        # Get personalized video URL
GET  /subscriptions/:id/video/status # Check video processing status

6.4 Screenshot Endpoints

# Authenticated (Users)
POST /screenshots                    # Submit screenshot
GET  /screenshots                    # List my screenshots
GET  /screenshots/:id                # Get screenshot details
GET  /screenshots/:id/status         # Check verification status

# Admin
GET  /admin/screenshots              # List all screenshots
GET  /admin/screenshots/pending      # Pending review
PUT  /admin/screenshots/:id/approve  # Manual approve
PUT  /admin/screenshots/:id/reject   # Manual reject

6.5 Transaction Endpoints

# Authenticated (Users)
GET  /transactions                   # List my transactions
GET  /transactions/:id               # Get transaction details
POST /transactions/withdraw          # Request withdrawal
GET  /transactions/balance           # Get wallet balance

# Admin
GET  /admin/transactions             # List all transactions
PUT  /admin/transactions/:id/approve # Approve withdrawal
PUT  /admin/transactions/:id/reject  # Reject withdrawal

6.6 Deposit Endpoints (Advertisers)

# Authenticated (Advertisers)
POST /api/deposits                   # Create deposit (returns Stripe checkout URL)
GET  /api/deposits                   # List my deposits
GET  /api/deposits/:id               # Get deposit details

# Stripe Webhook
POST /api/deposits/webhook/stripe    # Stripe webhook handler

6.7 Referral Endpoints

# Authenticated (Users)
GET  /referrals/code                 # Get my referral code
POST /referrals/validate             # Validate a referral code
GET  /referrals/stats                # Get referral statistics
GET  /referrals/history              # List referred users
GET  /referrals/friend-activity      # Friend activity feed
GET  /referrals/leaderboard          # Referral leaderboard
GET  /referrals/achievements         # My achievements
GET  /referrals/streaks/status       # Streak status
POST /referrals/streaks/use-shield   # Use streak shield
GET  /referrals/tiers                # Tier information
GET  /referrals/social-proof         # Platform stats (public)
GET  /referrals/dashboard            # Combined dashboard data

6.8 Support Endpoints

# Authenticated (Users & Advertisers)
POST /support-tickets                # Create ticket
GET  /support-tickets                # List my tickets
GET  /support-tickets/:id            # Get ticket details
POST /support-tickets/:id/messages   # Add message

# Admin
GET  /admin/support-tickets          # List all tickets
PUT  /admin/support-tickets/:id/assign   # Assign ticket
PUT  /admin/support-tickets/:id/resolve  # Resolve ticket

6.9 Admin Endpoints

# Users
GET  /admin/users                    # List all users
GET  /admin/users/:id                # Get user details
PUT  /admin/users/:id/ban            # Ban user
PUT  /admin/users/:id/unban          # Unban user

# Advertisers
GET  /admin/advertisers              # List all advertisers
GET  /admin/advertisers/:id          # Get advertiser details
PUT  /admin/advertisers/:id/approve  # Approve advertiser
PUT  /admin/advertisers/:id/suspend  # Suspend advertiser

# Analytics
GET  /admin/analytics/overview       # Platform overview
GET  /admin/analytics/users          # User metrics
GET  /admin/analytics/revenue        # Revenue metrics
GET  /admin/analytics/geographic     # Geographic distribution

# System
GET  /admin/system/health            # System health check
GET  /admin/system/activity-logs     # Activity logs

6.10 Webhook Endpoints

# Internal (Pub/Sub)
POST /webhooks/screenshot-verified   # Screenshot verification result
POST /webhooks/video-personalized    # Video personalization complete
POST /webhooks/payment-processed     # Payment processing result

# External
POST /api/deposits/webhook/stripe    # Stripe payment events
POST /api/stripe-billing/webhook     # Stripe billing events

7. Service Architecture

7.1 System Overview

┌──────────────────────────────────────────────────────────────────────────────────┐
│                           ENEZA ARCHITECTURE                                     │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  CLIENTS                                                                         │
│  ═══════                                                                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐            │
│  │   Mobile    │  │   Admin     │  │   Website   │  │   CEO       │            │
│  │   App       │  │  Dashboard  │  │  (Grow)     │  │  Dashboard  │            │
│  │  (Flutter)  │  │   (React)   │  │  (Next.js)  │  │  (Ext.)     │            │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘            │
│         │                │                │                │                    │
│         └────────────────┴────────┬───────┴────────────────┘                    │
│                                   │                                              │
│  API LAYER                        ▼                                              │
│  ═════════               ┌─────────────────┐                                    │
│                          │   ENEZA API     │                                    │
│                          │  (Node.js/TS)   │                                    │
│                          │   Cloud Run     │                                    │
│                          └────────┬────────┘                                    │
│                                   │                                              │
│  MESSAGE BROKER                   ▼                                              │
│  ══════════════          ┌─────────────────┐                                    │
│                          │  Google Pub/Sub │                                    │
│                          │  (Message Bus)  │                                    │
│                          └────────┬────────┘                                    │
│                                   │                                              │
│         ┌─────────────────────────┼─────────────────────────┐                   │
│         │                         │                         │                   │
│         ▼                         ▼                         ▼                   │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐             │
│  │  NOTIFICATION   │    │   SCREENSHOT    │    │     MEDIA       │             │
│  │    WORKER       │    │   VERIFIER      │    │    MANAGER      │             │
│  │   (Node.js)     │    │   (Python/ML)   │    │    (Python)     │             │
│  │   Cloud Run     │    │   Cloud Run     │    │   Cloud Run     │             │
│  └─────────────────┘    └─────────────────┘    └─────────────────┘             │
│         │                         │                         │                   │
│         │                         │                         │                   │
│  STORAGE LAYER                    │                         │                   │
│  ═════════════                    │                         │                   │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐             │
│  │    Neon        │    │  Cloudflare R2  │    │    Gemini AI    │             │
│  │   PostgreSQL    │    │  (Object Store) │    │   (Vision API)  │             │
│  └─────────────────┘    └─────────────────┘    └─────────────────┘             │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

7.2 Microservices

eneza-api (Main Backend)

  • Runtime: Node.js/TypeScript
  • Framework: Express.js
  • Database: Prisma ORM → Neon PostgreSQL
  • Hosting: Google Cloud Run
  • Responsibilities:
    • REST API for all clients
    • Authentication (JWT)
    • Business logic
    • Message publishing (Pub/Sub)
    • Stripe integration

eneza-notification-worker

  • Runtime: Node.js/TypeScript
  • Hosting: Google Cloud Run (always-on)
  • Responsibilities:
    • Process Pub/Sub messages
    • Send push notifications (FCM)
    • Send SMS notifications
    • Send email notifications
    • Process transaction callbacks

eneza-screenshot-verifier

  • Runtime: Python 3.11
  • AI: Google Gemini Vision API
  • Hosting: Google Cloud Run
  • Responsibilities:
    • Multi-check verification pipeline
    • QR code detection and validation
    • Image-to-image matching
    • View count extraction
    • Fraud detection scoring

eneza-media-manager

  • Runtime: Python 3.11
  • Libraries: FFmpeg, OpenCV, QRCode
  • Hosting: Google Cloud Run
  • Storage: Cloudflare R2
  • Responsibilities:
    • Video personalization
    • QR code watermarking
    • Video transcoding (multiple resolutions)
    • Thumbnail generation

7.3 Ad Distribution Flow

┌──────────────────────────────────────────────────────────────────────────────────┐
│                      AD DISTRIBUTION FLOW                                        │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  1. ADVERTISER CREATES CAMPAIGN                                                  │
│  ═════════════════════════════════                                               │
│                                                                                  │
│  ┌─────────────┐                    ┌─────────────┐                             │
│  │ Advertiser  │───Upload Video────▶│   R2/S3     │                             │
│  │  Dashboard  │                    │   Storage   │                             │
│  └─────────────┘                    └─────────────┘                             │
│         │                                  │                                     │
│         │ Create Campaign                  │ Transcode to variants              │
│         ▼                                  ▼                                     │
│  ┌─────────────┐                    ┌─────────────┐                             │
│  │  Eneza API  │───────────────────▶│   Media     │                             │
│  │             │    Pub/Sub         │   Manager   │                             │
│  └─────────────┘                    └─────────────┘                             │
│                                            │                                     │
│  2. USER SUBSCRIBES                        │                                     │
│  ═══════════════════                       ▼                                     │
│                                     ┌─────────────┐                             │
│  ┌─────────────┐                    │  Variants:  │                             │
│  │  Mobile     │───Subscribe───────▶│  - 1080p    │                             │
│  │    App      │                    │  - 720p     │                             │
│  └─────────────┘                    │  - 480p     │                             │
│         │                           └─────────────┘                             │
│         │ Generate unique code                                                   │
│         ▼                                                                        │
│  ┌─────────────┐                    ┌─────────────┐                             │
│  │  Eneza API  │───Personalize─────▶│   Media     │                             │
│  │             │    Pub/Sub         │   Manager   │                             │
│  └─────────────┘                    └─────────────┘                             │
│                                            │                                     │
│                                            │ Add QR watermark                    │
│                                            │ with subscription code              │
│                                            ▼                                     │
│  3. USER DOWNLOADS PERSONALIZED VIDEO      │                                     │
│  ════════════════════════════════════      │                                     │
│                                     ┌─────────────┐                             │
│  ┌─────────────┐                    │ Personalized│                             │
│  │  Mobile     │◀──Download────────│   Video     │                             │
│  │    App      │                    │ (with QR)   │                             │
│  └─────────────┘                    └─────────────┘                             │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

7.4 Screenshot Verification Flow

┌──────────────────────────────────────────────────────────────────────────────────┐
│                   SCREENSHOT VERIFICATION FLOW                                   │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐               │
│  │   Mobile    │──POST──▶│  Eneza API  │──Pub/Sub▶│ Screenshot  │               │
│  │     App     │         │             │         │  Verifier   │               │
│  └─────────────┘         └─────────────┘         └─────────────┘               │
│                                                         │                        │
│                                                         ▼                        │
│                                            ┌────────────────────────┐           │
│                                            │  VERIFICATION PIPELINE │           │
│                                            ├────────────────────────┤           │
│                                            │                        │           │
│                                            │  1. FORMAT CHECK       │           │
│                                            │     • Image valid?     │           │
│                                            │     • Resolution OK?   │           │
│                                            │     • Not corrupted?   │           │
│                                            │                        │           │
│                                            │  2. QR CODE CHECK      │           │
│                                            │     • QR detected?     │           │
│                                            │     • Code matches     │           │
│                                            │       subscription?    │           │
│                                            │     • Not tampered?    │           │
│                                            │                        │           │
│                                            │  3. IMAGE MATCHING     │           │
│                                            │     • Ad visible?      │           │
│                                            │     • Content matches  │           │
│                                            │       original ad?     │           │
│                                            │                        │           │
│                                            │  4. VIEW COUNTER       │           │
│                                            │     (Gemini Vision)    │           │
│                                            │     • Extract view #   │           │
│                                            │     • Validate range   │           │
│                                            │     • Check for fraud  │           │
│                                            │                        │           │
│                                            │  5. TIMESTAMP CHECK    │           │
│                                            │     • 20-24 hours      │           │
│                                            │       elapsed?         │           │
│                                            │                        │           │
│                                            │  6. DUPLICATE CHECK    │           │
│                                            │     • Not submitted    │           │
│                                            │       before?          │           │
│                                            │                        │           │
│                                            └────────────┬───────────┘           │
│                                                         │                        │
│                                                         ▼                        │
│                                            ┌────────────────────────┐           │
│                                            │  CALCULATE FINAL SCORE │           │
│                                            │                        │           │
│                                            │  Score = Weighted avg  │           │
│                                            │  of all check scores   │           │
│                                            │                        │           │
│                                            │  • > 0.8 = VERIFIED    │           │
│                                            │  • 0.5-0.8 = REVIEW    │           │
│                                            │  • < 0.5 = REJECTED    │           │
│                                            │  • Breaking check fail │           │
│                                            │    = FRAUD/REJECTED    │           │
│                                            └────────────┬───────────┘           │
│                                                         │                        │
│                                                         ▼                        │
│  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐               │
│  │   Mobile    │◀─Notify─│ Notification│◀─Pub/Sub│  Eneza API  │               │
│  │     App     │         │   Worker    │         │ (DB Update) │               │
│  └─────────────┘         └─────────────┘         └─────────────┘               │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

7.5 Payment Flow

┌──────────────────────────────────────────────────────────────────────────────────┐
│                         PAYMENT FLOWS                                            │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ADVERTISER DEPOSIT (Credit Card → USD Credits)                                  │
│  ═════════════════════════════════════════════                                   │
│                                                                                  │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐   │
│  │ Advertiser  │────▶│  Eneza API  │────▶│   Stripe    │────▶│  Webhook    │   │
│  │  Dashboard  │     │ Create      │     │  Checkout   │     │  Handler    │   │
│  └─────────────┘     │ Session     │     └─────────────┘     └─────────────┘   │
│                      └─────────────┘            │                   │           │
│                                                 │                   │           │
│                            ┌────────────────────┘                   │           │
│                            │ Payment Success                        │           │
│                            ▼                                        ▼           │
│                      ┌─────────────┐                         ┌─────────────┐   │
│                      │  Advertiser │ ◀───Credits Added───── │  Eneza API  │   │
│                      │   Account   │                         │ Process     │   │
│                      └─────────────┘                         └─────────────┘   │
│                                                                                  │
│  USER WITHDRAWAL (USD Balance → Mobile Money)                                    │
│  ═══════════════════════════════════════════                                    │
│                                                                                  │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐   │
│  │   Mobile    │────▶│  Eneza API  │────▶│  Payment    │────▶│  Mobile     │   │
│  │     App     │     │ Withdrawal  │     │  Processor  │     │  Money      │   │
│  │ Request     │     │  Request    │     │  (MTN/etc)  │     │  Account    │   │
│  └─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘   │
│                             │                   │                               │
│                             │                   │                               │
│                             ▼                   ▼                               │
│                      ┌─────────────┐     ┌─────────────┐                        │
│                      │  Balance    │     │  Callback   │                        │
│                      │  Deducted   │◀────│  Received   │                        │
│                      │  (Pending)  │     │ (Success)   │                        │
│                      └─────────────┘     └─────────────┘                        │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

8. Authentication & Authorization

8.1 User Authentication (Phone + OTP)

typescript
// Flow:
// 1. User enters phone number
// 2. System sends OTP via SMS (Twilio/Africa's Talking)
// 3. User enters OTP
// 4. System issues JWT + refresh token

// JWT Payload
interface UserJwtPayload {
  userId: string;
  phoneNumber: string;
  countryId: string;
  tier: string;
  isVerified: boolean;
  isPremium: boolean;
}

// Token Expiry
const ACCESS_TOKEN_EXPIRY = '15m';   // 15 minutes
const REFRESH_TOKEN_EXPIRY = '30d';  // 30 days

8.2 Advertiser Authentication (Email + Password)

typescript
// Flow:
// 1. Advertiser registers with email + password
// 2. System sends verification email
// 3. Advertiser verifies email
// 4. Advertiser logs in, receives JWT

// JWT Payload
interface AdvertiserJwtPayload {
  advertiserId: string;
  email: string;
  companyName: string | null;
  isVerified: boolean;
  status: AdvertiserStatus;
}

8.3 Admin Authentication

typescript
// Separate admin auth with role-based access
interface AdminJwtPayload {
  adminId: string;
  email: string;
  role: 'SUPER_ADMIN' | 'ADMIN' | 'MODERATOR' | 'SUPPORT';
  permissions: string[];
}

8.4 Authorization Middleware

typescript
// Role-based middleware
app.use('/admin', verifyToken, isAdmin);
app.use('/advertisers', verifyToken, isAdvertiser);
app.use('/users', verifyToken, isUser);

// Permission checks
const canApproveAds = hasPermission('ads:approve');
const canBanUsers = hasPermission('users:ban');
const canProcessPayments = hasPermission('payments:process');

9. Billing System

9.1 Advertiser Billing

Credit System

  • Advertisers deposit USD via Stripe
  • Credits are added to their account (1 credit = $1 USD)
  • Volume discounts available:
    • $100+: 5% bonus
    • $500+: 10% bonus
    • $1,000+: 15% bonus
    • $5,000+: 20% bonus

Campaign Budgeting

  • Each campaign has a USD budget
  • Budget is deducted as views are verified
  • Campaign pauses when budget exhausted
  • Cost per view varies by country tier

Stripe Integration

typescript
// Checkout session creation
const session = await stripe.checkout.sessions.create({
  customer: advertiser.stripeCustomerId,
  mode: 'payment',
  line_items: [{
    price_data: {
      currency: 'usd',
      product_data: { name: 'Eneza Ad Credits' },
      unit_amount: amountCents,
    },
    quantity: 1,
  }],
  success_url: `${baseUrl}/deposits/success`,
  cancel_url: `${baseUrl}/deposits/cancel`,
  metadata: {
    depositId,
    advertiserId,
    bonusPercent,
  },
});

9.2 User Payouts

Earnings Calculation

typescript
// Per-view earnings formula
const earnings = baseRate 
  * viewCount 
  * tierMultiplier 
  * streakMultiplier;

// Example:
// $0.002/view × 50 views × 1.2 (Gold tier) × 1.1 (7-day streak)
// = $0.132 USD

Withdrawal Process

  1. User requests withdrawal (min $0.50 - $2 depending on country)
  2. System converts USD to local currency at current rate
  3. Transaction created with PENDING status
  4. Admin approves (or auto-approve for trusted users)
  5. Payment processor API called
  6. Callback received, transaction marked COMPLETED

Payment Processors by Country

CountryProcessorCurrency
KenyaM-PesaKES
NigeriaFlutterwaveNGN
South AfricaPayFastZAR
GhanaMTN MoMoGHS
UgandaAirtel MoneyUGX
TanzaniaVodacom M-PesaTZS
EswatiniMTN MoMoSZL

9.3 Invoice System

typescript
// Invoice generation
const invoice = await invoiceService.create({
  advertiserId,
  depositId,
  items: [{
    description: 'Ad Credits Deposit',
    quantity: 1,
    unitPrice: amount,
  }],
  taxRate: 0, // No VAT currently
});

// PDF generation via Cloudflare Pages Function
const pdfUrl = await generateInvoicePdf(invoice);

10. Verification System

10.1 Verification Pipeline

The screenshot verification system uses a multi-check pipeline where each check contributes to an overall verification score.

python
# Pipeline configuration
VERIFICATION_CHECKS = [
    {
        "name": "format_check",
        "weight": 0.1,
        "is_breaking": True,  # Fails immediately if this fails
    },
    {
        "name": "qr_code_check", 
        "weight": 0.9,
        "is_breaking": True,
    },
    {
        "name": "image_matching",
        "weight": 0.7,
        "is_breaking": False,  # Contributes to aggregate score
    },
    {
        "name": "view_counter",
        "weight": 0.6,
        "is_breaking": False,
    },
    {
        "name": "timestamp_check",
        "weight": 0.8,
        "is_breaking": True,
    },
    {
        "name": "duplicate_check",
        "weight": 1.0,
        "is_breaking": True,
    },
]

10.2 Individual Checks

Format Check

  • Image is valid (PNG, JPEG)
  • Resolution within acceptable range
  • File not corrupted

QR Code Check

  • QR code detected in image
  • QR code decodes successfully
  • Decoded value matches subscription code
  • QR position consistent with watermark placement

Image Matching

  • Ad content visible in screenshot
  • Color histogram comparison with original
  • Feature matching (ORB/SIFT)
  • Structural similarity (SSIM)

View Counter (Gemini Vision AI)

python
# Gemini prompt for view extraction
prompt = """
Analyze this screenshot of a WhatsApp status view.
Extract the view count number shown in the screenshot.
Look for a number indicating how many people have viewed the status.

Return JSON:
{
  "view_count": <number or null>,
  "confidence": <0.0 to 1.0>,
  "is_whatsapp_status": <true/false>,
  "additional_info": "<observations>"
}
"""

Timestamp Check

  • Subscription was 20-24 hours ago (WhatsApp 24h limit)
  • Screenshot not submitted too early
  • Not expired (past 24h window)

Duplicate Check

  • Image hash not seen before
  • Perceptual hash (pHash) comparison
  • Same user hasn't submitted similar image

10.3 Fraud Detection

python
# Fraud indicators
FRAUD_SIGNALS = [
    "qr_code_missing",           # No QR watermark found
    "qr_code_mismatch",          # QR doesn't match subscription
    "view_count_suspiciously_high",  # > 500 views
    "view_count_mismatch",       # Extracted ≠ submitted
    "image_too_similar",         # Near-duplicate of previous
    "timestamp_invalid",         # Submitted too early/late
    "device_fingerprint_mismatch",   # Different device
    "rapid_submissions",         # Too many submissions quickly
]

# Auto-ban threshold
CRITICAL_FAILURE_THRESHOLD = 3  # 3 critical failures = ban

10.4 Manual Review Queue

Screenshots with borderline scores (0.5-0.8) go to manual review:

typescript
// Admin review actions
await screenshotService.manualApprove(screenshotId, {
  adminId,
  reason: 'QR visible, view count verified manually',
  adjustedViews: 45,  // Can adjust if needed
});

await screenshotService.manualReject(screenshotId, {
  adminId,
  reason: 'Screenshot appears edited',
  markAsFraud: true,  // Increment user's fraud count
});

11. Notifications

11.1 Notification Types

Push Notifications (FCM)

  • New ad available in your country
  • Subscription expiring soon
  • Screenshot verified (with earnings)
  • Screenshot rejected (with reason)
  • Withdrawal processed
  • Achievement unlocked
  • Streak at risk

Email Notifications

  • Advertiser: Welcome email
  • Advertiser: Email verification
  • Advertiser: Deposit confirmation
  • Advertiser: Campaign performance weekly digest

Admin Notifications

  • New user registration
  • New advertiser signup
  • Large deposit received
  • Withdrawal request pending
  • Fraud alert triggered
  • Support ticket created

11.2 Notification Worker

typescript
// Pub/Sub topic handlers
const handlers = {
  'screenshot-verified': async (data) => {
    // Send push to user
    await fcmService.send(data.userId, {
      title: 'Screenshot Verified! 🎉',
      body: `You earned $${data.earnings} from ${data.viewCount} views!`,
    });
  },
  
  'withdrawal-completed': async (data) => {
    // Send push + SMS
    await fcmService.send(data.userId, {...});
    await smsService.send(data.phoneNumber, `Your withdrawal of ${data.amount} has been sent!`);
  },
  
  'streak-at-risk': async (data) => {
    // Reminder to maintain streak
    await fcmService.send(data.userId, {
      title: 'Your streak is at risk! 🔥',
      body: 'Submit a screenshot today to keep your streak alive!',
    });
  },
};

11.3 Transactional Notifications

typescript
// HTTP-based notifications for critical events
await sendTransactionalNotification(userId, {
  type: 'EARNING',
  title: 'You earned money!',
  body: `$${amount} added to your wallet`,
  data: {
    screenshotId,
    subscriptionId,
    amount,
  },
});

12. Analytics & Reporting

12.1 Platform Metrics

typescript
// Dashboard overview
interface DashboardMetrics {
  users: {
    total: number;
    active30d: number;
    newToday: number;
    newThisWeek: number;
  };
  advertisers: {
    total: number;
    active: number;
    newThisMonth: number;
  };
  campaigns: {
    total: number;
    active: number;
    totalBudgetUsd: number;
  };
  revenue: {
    totalDepositsUsd: number;
    totalPayoutsUsd: number;
    platformFeesUsd: number;
  };
  screenshots: {
    totalSubmitted: number;
    verifiedToday: number;
    rejectedToday: number;
    pendingReview: number;
  };
}

12.2 Advertiser Analytics

typescript
// Campaign performance
interface CampaignAnalytics {
  views: {
    total: number;
    verified: number;
    pending: number;
    rejected: number;
  };
  subscriptions: {
    total: number;
    active: number;
    completed: number;
    expired: number;
  };
  spend: {
    totalUsd: number;
    costPerView: number;
    remainingBudget: number;
  };
  geographic: {
    [countryCode: string]: {
      views: number;
      subscriptions: number;
    };
  };
  timeline: {
    date: string;
    views: number;
    subscriptions: number;
    spend: number;
  }[];
}

12.3 User Analytics

typescript
// User performance
interface UserAnalytics {
  earnings: {
    totalUsd: number;
    thisMonthUsd: number;
    pendingUsd: number;
    withdrawnUsd: number;
  };
  screenshots: {
    total: number;
    approved: number;
    rejected: number;
    approvalRate: number;
  };
  referrals: {
    total: number;
    active: number;
    bonusEarnedUsd: number;
  };
  streak: {
    current: number;
    longest: number;
    multiplier: number;
  };
  tier: {
    name: string;
    multiplier: number;
    progress: number;  // To next tier
  };
}

12.4 CEO Dashboard Integration

The Eneza API exposes metrics for the CEO dashboard:

typescript
// GET /api/dashboard/metrics
interface EnezaDashboardMetrics {
  totalUsers: number;
  activeUsersLast30Days: number;
  totalAdvertisers: number;
  activeCampaigns: number;
  totalRevenueUsd: number;
  totalPayoutsUsd: number;
  platformProfitUsd: number;
  screenshotsVerifiedToday: number;
  fraudRate: number;  // % of rejected screenshots
}

13. Technical Stack

13.1 Backend

ComponentTechnology
RuntimeNode.js 20 (TypeScript)
FrameworkExpress.js 4.x
ORMPrisma 5.x
DatabasePostgreSQL 15 (Neon Serverless)
CachingRedis (Upstash)
Message QueueGoogle Cloud Pub/Sub
Object StorageCloudflare R2
PaymentsStripe
Push NotificationsFirebase Cloud Messaging (FCM)
SMSTwilio / Africa's Talking
EmailResend
AI/MLGoogle Gemini Vision API

13.2 Frontend (Admin Dashboard)

ComponentTechnology
FrameworkReact 18
Build ToolVite
StylingTailwind CSS
State ManagementTanStack Query (React Query)
ChartsRecharts
HostingCloudflare Pages

13.3 Infrastructure

ComponentTechnology
API HostingGoogle Cloud Run
CDNCloudflare
DNSCloudflare
CI/CDGoogle Cloud Build
MonitoringGoogle Cloud Logging
Error TrackingSentry

13.4 Mobile App

ComponentTechnology
FrameworkFlutter
State ManagementRiverpod
API ClientDio
DistributionGoogle Play / Direct APK

14. Roadmap

Q2 2026 (Current)

  • [x] Core platform live with 12k+ users
  • [x] AI-powered screenshot verification
  • [x] Mobile money payouts (Kenya, Nigeria, South Africa)
  • [x] Stripe billing for advertisers
  • [x] Admin dashboard
  • [x] Referral system with tiers
  • [ ] IN PROGRESS: Multi-language support
  • [ ] IN PROGRESS: WhatsApp Business API integration

Q3 2026

  • [ ] Self-serve advertiser onboarding
  • [ ] Campaign A/B testing
  • [ ] Advanced fraud detection (ML model)
  • [ ] iOS app launch
  • [ ] WhatsApp status direct posting (via WA Business API)
  • [ ] Expand to 10 more African countries

Q4 2026

  • [ ] Agency dashboard (manage multiple advertisers)
  • [ ] Influencer tiers (premium posters with larger audiences)
  • [ ] Real-time bidding for ad slots
  • [ ] Integration with WhatsApp Channels
  • [ ] API for third-party integrations

2027

  • [ ] Instagram/TikTok status expansion
  • [ ] Brand ambassador program
  • [ ] Enterprise features
  • [ ] White-label platform licensing

15. Gaps & Needed Improvements

15.1 Critical Gaps

Video Personalization Reliability

  • Issue: Media Manager occasionally fails to generate personalized videos
  • Impact: Users can't download videos, blocking the flow
  • Needed: Better error handling, retry logic, fallback to non-personalized video

Payment Processor Integration

  • Issue: Only MTN MoMo fully integrated; other processors have limited support
  • Impact: Users in some countries can't withdraw
  • Needed: Complete integration for Airtel Money, Orange Money, Flutterwave

Screenshot Verification Accuracy

  • Issue: ~15% of screenshots go to manual review
  • Impact: Admin overhead, delayed payouts
  • Needed: Improve ML model, better training data, reduce false positives

15.2 Feature Gaps

Missing Advertiser Features

  • No campaign scheduling (start/end dates)
  • No audience targeting beyond country
  • No ad preview before going live
  • No real-time campaign performance alerts
  • No bulk campaign management

Missing User Features

  • No in-app help/tutorials
  • No earnings history export
  • No push notification preferences
  • No account deletion (GDPR compliance)
  • No multiple payment methods per user

Missing Admin Features

  • No bulk screenshot review
  • No automated fraud banning (currently manual)
  • No financial reconciliation reports
  • No advertiser credit management UI
  • No system health alerts

15.3 Technical Debt

Database

  • Some queries not optimized (full table scans)
  • Missing indexes on frequently queried columns
  • Denormalized data sometimes out of sync (AdStats)
  • Need to implement database connection pooling

API

  • Inconsistent error response formats
  • Missing rate limiting on some endpoints
  • No API versioning (/v1/, /v2/)
  • OpenAPI/Swagger documentation incomplete

Codebase

  • Some services have tight coupling
  • Test coverage < 50%
  • Environment configuration scattered
  • Logging inconsistent across services

15.4 Scalability Concerns

Database

  • Single Neon instance (no read replicas)
  • Screenshot images stored as URLs, not properly archived
  • Transaction history growing unbounded

Message Queue

  • No dead letter queue for failed messages
  • No message deduplication
  • Retry logic not consistent across handlers

Video Processing

  • Single media-manager instance
  • No queue depth monitoring
  • Large videos timeout

15.5 Security Improvements Needed

  • [ ] Implement API key rotation for advertisers
  • [ ] Add 2FA for admin accounts
  • [ ] Encrypt sensitive fields in database (account numbers)
  • [ ] Implement request signing for webhooks
  • [ ] Add IP whitelisting for admin endpoints
  • [ ] Regular security audits

15.6 Compliance Gaps

  • [ ] GDPR: No data export/deletion workflows
  • [ ] POPIA (South Africa): Privacy policy needs update
  • [ ] Financial regulations: KYC not implemented for high-value users
  • [ ] Tax compliance: No automatic tax calculation for payouts

15.7 Priority Fixes (Next Sprint)

  1. Fix video personalization failures - Critical blocker
  2. Add missing payment processors - Revenue blocker
  3. Improve verification accuracy - Reduces manual work
  4. Implement API rate limiting - Security
  5. Add database indexes - Performance

Appendix A: Environment Variables

bash
# Database
DATABASE_URL=postgresql://...@neon.tech/eneza

# Authentication
JWT_SECRET=...
JWT_REFRESH_SECRET=...
ADMIN_JWT_SECRET=...

# Stripe
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PUBLISHABLE_KEY=pk_live_...

# Google Cloud
GCP_PROJECT_ID=eneza-...
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json

# Gemini AI
GEMINI_API_KEY=AIza...

# Cloudflare R2
R2_ACCOUNT_ID=...
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...
R2_BUCKET_NAME=eneza-media

# Firebase (FCM)
FIREBASE_PROJECT_ID=...
FIREBASE_PRIVATE_KEY=...
FIREBASE_CLIENT_EMAIL=...

# SMS
TWILIO_ACCOUNT_SID=...
TWILIO_AUTH_TOKEN=...
TWILIO_PHONE_NUMBER=+1...

# Email
RESEND_API_KEY=re_...

# Admin
ADMIN_NOTIFICATION_EMAIL=admin@eneza.app
INITIAL_ADMIN_EMAIL=...
INITIAL_ADMIN_PASSWORD=...

Appendix B: API Error Codes

CodeDescription
AUTH_001Invalid or expired token
AUTH_002OTP expired
AUTH_003Invalid OTP
AUTH_004Email not verified
USER_001User not found
USER_002User blocked
USER_003Invalid phone number
AD_001Ad not found
AD_002Ad not active
AD_003Ad budget exhausted
SUB_001Already subscribed
SUB_002Subscription expired
SUB_003Max subscriptions reached
SCREEN_001Invalid screenshot format
SCREEN_002Verification failed
SCREEN_003Duplicate screenshot
PAY_001Insufficient balance
PAY_002Below minimum withdrawal
PAY_003Payment processor error

Document generated from codebase analysis. Last updated: March 2026.

One chat. Everything done.