Skip to content

Zaptam Billing & Wallet System

Deep dive into membership tiers, wallet functionality, transactions, and payouts.


Business Model

Zaptam uses a gender-differentiated monetization model:

┌────────────────────────────────────────────────────────────┐
│                     ZAPTAM ECONOMY                         │
├────────────────────────────────────────────────────────────┤
│                                                            │
│    MALE USERS (Spenders)        FEMALE USERS (Earners)    │
│    ─────────────────────        ──────────────────────    │
│                                                            │
│    • Pay membership tiers       • Free access              │
│    • Purchase credits           • Earn from engagement     │
│    • Pay for boosts             • Request withdrawals      │
│                                                            │
│         $49 / $149 / $499            $0 entry              │
│            per month                                       │
│                                                            │
└────────────────────────────────────────────────────────────┘

Membership Tiers

File: Landing page src/pages/Membership.tsx

Standard Tier - $49/month

typescript
{
  name: 'Standard',
  price: 49,
  period: 'month',
  description: 'Enter the network with full access to verified members.',
  features: [
    'Worth verification included',
    'Browse verified profiles',
    'Express interest unlimited',
    'Full messaging access',
    'Basic privacy controls',
    'Standard support',
  ],
}

Premium Tier - $149/month

typescript
{
  name: 'Premium',
  price: 149,
  period: 'month',
  description: 'Signal your seriousness with priority placement and verification badge.',
  features: [
    'Everything in Standard',
    'Worth verification badge',
    'Priority in discovery',
    'See who viewed your profile',
    'Advanced privacy controls',
    'Read receipts control',
    'Priority support',
  ],
  badge: 'Most Popular',
}

Elite Tier - $499/month

typescript
{
  name: 'Elite',
  price: 499,
  period: 'month',
  description: 'The full experience with personal curation and exclusive perks.',
  features: [
    'Everything in Premium',
    'Top discovery placement',
    'Personal curator access',
    'Exclusive member events',
    'Profile boost monthly',
    'Concierge support 24/7',
    'Priority match suggestions',
  ],
}

Women's Access - $0

typescript
{
  name: 'Women Join Free',
  price: 0,
  description: 'Full access after passing identity verification and acceptance review.',
  features: [
    'Identity verification required',
    'Full platform access',
    'Unlimited browsing',
    'Unlimited messaging',
    'All privacy features',
    'Priority support',
  ],
}

Tier Comparison

FeatureStandardPremiumElite
Worth Verification
Browse Profiles
Unlimited Messaging
Worth Badge
Priority Discovery
Profile Views
Personal Curator
Exclusive Events
24/7 Concierge

Wallet Service

File: src/services/wallet.service.ts

Balance Calculation

typescript
export async function getWalletBalance(userId: string) {
  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: { gender: true },
  });

  // Get all completed transactions
  const transactions = await prisma.walletTransaction.findMany({
    where: {
      userId,
      status: 'COMPLETED',
    },
  });

  // Calculate running balance
  let balance = new Decimal(0);

  for (const tx of transactions) {
    if (tx.type === 'EARNING' || tx.type === 'CREDIT_PURCHASE') {
      balance = balance.plus(tx.amount);  // Credits IN
    } else if (tx.type === 'WITHDRAWAL' || tx.type === 'BOOST' || tx.type === 'MEMBERSHIP') {
      balance = balance.minus(tx.amount);  // Credits OUT
    }
  }

  return {
    balance: balance.toNumber(),
    currency: 'USD',
    userType: user.gender === 'FEMALE' ? 'earner' : 'spender',
  };
}

Balance Formula:

Balance = Σ(Credits In) - Σ(Credits Out)

Credits In:
  - EARNING (female engagement rewards)
  - CREDIT_PURCHASE (male credit buys)

Credits Out:
  - WITHDRAWAL (female payouts)
  - BOOST (profile boost purchases)
  - MEMBERSHIP (subscription payments)

Transaction Types

prisma
enum TransactionType {
  MEMBERSHIP       // Monthly subscription payment
  CREDIT_PURCHASE  // Buying credits (male)
  EARNING          // Platform earnings (female)
  WITHDRAWAL       // Cashing out (female)
  BOOST            // Profile boost purchase
}

Transaction Flow

Male Users:

Credit Purchase → CREDIT_PURCHASE (Completed)

                    Balance +$X

Use credits   → BOOST/MEMBERSHIP (Completed)

                    Balance -$X

Female Users:

Platform engagement → EARNING (Completed)

                      Balance +$X

Request payout → WITHDRAWAL (Pending)

Admin approves → WITHDRAWAL (Completed)

                      Balance -$X

Transaction Statuses

prisma
enum TransactionStatus {
  PENDING    // Processing/awaiting action
  COMPLETED  // Successfully processed
  FAILED     // Transaction failed
  REFUNDED   // Reversed/refunded
}

Status Transitions

           ┌─────────┐
           │ PENDING │
           └────┬────┘

    ┌───────────┼───────────┐
    │           │           │
    ▼           ▼           ▼
┌───────┐  ┌─────────┐  ┌────────┐
│ FAILED│  │COMPLETED│  │REFUNDED│
└───────┘  └─────────┘  └────────┘

Credit Purchases (Male)

typescript
export async function purchaseCredits(
  userId: string,
  amount: number,
  paymentReference: string
): Promise<void> {
  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: { gender: true },
  });

  if (!user) {
    throw new NotFoundError('User not found');
  }

  // Gender check
  if (user.gender !== 'MALE') {
    throw new ForbiddenError('Only male users can purchase credits');
  }

  await prisma.walletTransaction.create({
    data: {
      userId,
      type: 'CREDIT_PURCHASE',
      amount: new Decimal(amount),
      status: 'COMPLETED',  // In production: PENDING until payment confirmed
      reference: paymentReference,
    },
  });
}

API Endpoint:

POST /api/wallet/purchase
{
  "amount": 100,
  "paymentReference": "stripe_pi_123456"
}

Membership Purchases

typescript
export async function purchaseMembership(
  userId: string,
  tierId: string,
  paymentReference: string
): Promise<void> {
  const tier = await prisma.membershipTier.findUnique({
    where: { id: tierId, isActive: true },
  });

  if (!tier) {
    throw new NotFoundError('Membership tier not found');
  }

  await prisma.walletTransaction.create({
    data: {
      userId,
      type: 'MEMBERSHIP',
      amount: tier.price,
      status: 'COMPLETED',  // In production: PENDING until payment confirmed
      reference: paymentReference,
    },
  });
}

Withdrawals (Female)

Request Withdrawal

typescript
export async function requestWithdrawal(
  userId: string,
  amount: number,
  payoutDetails: string
): Promise<void> {
  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: { gender: true },
  });

  if (!user) {
    throw new NotFoundError('User not found');
  }

  // Gender check
  if (user.gender !== 'FEMALE') {
    throw new ForbiddenError('Only female users can request withdrawals');
  }

  // Balance check
  const { balance } = await getWalletBalance(userId);
  if (balance < amount) {
    throw new BadRequestError('Insufficient balance');
  }

  // Minimum withdrawal
  if (amount < 10) {
    throw new BadRequestError('Minimum withdrawal amount is $10');
  }

  await prisma.walletTransaction.create({
    data: {
      userId,
      type: 'WITHDRAWAL',
      amount: new Decimal(amount),
      status: 'PENDING',  // Requires admin approval
      reference: payoutDetails,  // Payment method info
    },
  });
}

API Endpoint:

POST /api/wallet/withdraw
{
  "amount": 50,
  "payoutDetails": "PayPal: user@email.com"
}

Process Withdrawal (Admin)

typescript
export async function processWithdrawal(
  transactionId: string,
  approved: boolean,
  adminId: string
): Promise<void> {
  const transaction = await prisma.walletTransaction.findUnique({
    where: { id: transactionId },
  });

  if (!transaction) {
    throw new NotFoundError('Transaction not found');
  }

  if (transaction.type !== 'WITHDRAWAL' || transaction.status !== 'PENDING') {
    throw new BadRequestError('Invalid transaction for processing');
  }

  await prisma.walletTransaction.update({
    where: { id: transactionId },
    data: {
      status: approved ? 'COMPLETED' : 'FAILED',
      reference: `${transaction.reference}|processed_by:${adminId}`,
    },
  });
}

Admin Endpoint:

PATCH /api/admin/payouts/:id
{
  "approved": true
}

Transaction History

typescript
export async function getTransactionHistory(
  userId: string, 
  page: number, 
  limit: number
) {
  const skip = (page - 1) * limit;

  const [transactions, total] = await Promise.all([
    prisma.walletTransaction.findMany({
      where: { userId },
      orderBy: { createdAt: 'desc' },
      skip,
      take: limit,
    }),
    prisma.walletTransaction.count({ where: { userId } }),
  ]);

  return {
    items: transactions,
    total,
    page,
    limit,
    totalPages: Math.ceil(total / limit),
  };
}

API Response:

json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "tx-uuid",
        "type": "EARNING",
        "amount": 25.00,
        "currency": "USD",
        "status": "COMPLETED",
        "reference": "match_bonus_2024-01",
        "createdAt": "2024-01-15T10:30:00Z"
      },
      {
        "id": "tx-uuid-2",
        "type": "WITHDRAWAL",
        "amount": 100.00,
        "currency": "USD",
        "status": "PENDING",
        "reference": "PayPal: user@email.com",
        "createdAt": "2024-01-10T14:20:00Z"
      }
    ],
    "total": 15,
    "page": 1,
    "limit": 20,
    "totalPages": 1
  }
}

Database Schema

WalletTransaction Model

prisma
model WalletTransaction {
  id        String            @id @default(uuid()) @db.Uuid
  userId    String            @map("user_id") @db.Uuid
  user      User              @relation(fields: [userId], references: [id], onDelete: Cascade)
  type      TransactionType
  amount    Decimal           @db.Decimal(10, 2)
  currency  String            @default("USD")
  status    TransactionStatus @default(PENDING)
  reference String?
  createdAt DateTime          @default(now()) @map("created_at")

  @@index([userId])
  @@map("wallet_transactions")
}

MembershipTier Model

prisma
model MembershipTier {
  id        String   @id @default(uuid()) @db.Uuid
  name      String
  price     Decimal  @db.Decimal(10, 2)
  currency  String   @default("USD")
  duration  Int      // Days
  features  Json     // Array of feature strings
  isActive  Boolean  @default(true) @map("is_active")
  createdAt DateTime @default(now()) @map("created_at")

  @@map("membership_tiers")
}

Admin Dashboard

Transaction Overview

typescript
// GET /api/admin/transactions
const transactions = await prisma.walletTransaction.findMany({
  include: {
    user: {
      select: { id: true, alias: true, phoneNumber: true },
    },
  },
  orderBy: { createdAt: 'desc' },
  skip,
  take: limit,
});

Pending Payouts

typescript
// GET /api/admin/payouts?status=PENDING
const payouts = await prisma.walletTransaction.findMany({
  where: {
    type: 'WITHDRAWAL',
    status: 'PENDING',
  },
  include: {
    user: {
      select: { id: true, alias: true, phoneNumber: true, email: true },
    },
  },
  orderBy: { createdAt: 'desc' },
});

UI Components

Balance Card

typescript
// app/src/components/wallet/BalanceCard.tsx
interface BalanceCardProps {
  balance: number;
  currency: string;
  userType: 'earner' | 'spender';
}

export function BalanceCard({ balance, currency, userType }: BalanceCardProps) {
  return (
    <Card>
      <CardHeader>
        <CardTitle>
          {userType === 'earner' ? 'Your Earnings' : 'Your Credits'}
        </CardTitle>
      </CardHeader>
      <CardContent>
        <p className="text-4xl font-bold">
          ${balance.toFixed(2)} {currency}
        </p>
      </CardContent>
    </Card>
  );
}

Transaction Item

typescript
// app/src/components/wallet/TransactionItem.tsx
const getTypeIcon = (type: string) => {
  if (['EARNING', 'CREDIT_PURCHASE'].includes(type)) {
    return <ArrowDownRight className="text-green-400" />;  // Credit IN
  }
  return <ArrowUpRight className="text-red-400" />;  // Credit OUT
};

Withdrawal Modal

typescript
// app/src/components/wallet/WithdrawalModal.tsx
// For female users to request payouts
interface WithdrawalModalProps {
  balance: number;
  onSubmit: (amount: number, payoutDetails: string) => void;
}

Payment Integration Notes

Current State

The current implementation stores transactions with COMPLETED status immediately. In production:

  1. Payment Gateway Integration (Stripe recommended)

    • Create PaymentIntent
    • Store transaction as PENDING
    • Webhook confirms payment → update to COMPLETED
  2. Subscription Management

    • Use Stripe Subscriptions for recurring tiers
    • Handle subscription lifecycle events
  3. Payout Processing

    • Integrate with payout provider (PayPal, bank transfer)
    • Automated payout on admin approval
User initiates purchase


Create PENDING transaction


Redirect to Stripe Checkout


Payment succeeds → Webhook


Update transaction to COMPLETED


Credit user's balance

Revenue Metrics (Admin)

Dashboard Stats

typescript
// Calculate total revenue
const revenue = await prisma.walletTransaction.aggregate({
  where: {
    type: { in: ['MEMBERSHIP', 'CREDIT_PURCHASE', 'BOOST'] },
    status: 'COMPLETED',
  },
  _sum: {
    amount: true,
  },
});

// Pending payouts
const pendingPayouts = await prisma.walletTransaction.aggregate({
  where: {
    type: 'WITHDRAWAL',
    status: 'PENDING',
  },
  _sum: {
    amount: true,
  },
});

FAQ

Why different models for men and women?

"Men bring financial capacity, women bring authentic presence. Both sides are verified and contribute to the ecosystem differently."

What's the minimum withdrawal?

$10 minimum to reduce transaction overhead.

How are earnings calculated?

Currently not implemented. Potential earning triggers:

  • Receiving messages
  • Getting matches
  • Profile engagement
  • Referrals

One chat. Everything done.