Skip to content

YeboShops Payment System

Deep dive into the escrow-based Secure Payment system and wallet management.

System Overview

YeboShops uses an internal wallet system with escrow-based secure payments for P2P transactions. This eliminates the need for external payment gateways while providing buyer protection.

┌─────────────────────────────────────────────────────────────────┐
│                      Payment Flow                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────┐                              ┌──────────┐         │
│  │  Buyer   │                              │  Seller  │         │
│  │  Wallet  │                              │  Wallet  │         │
│  └────┬─────┘                              └─────┬────┘         │
│       │                                          │               │
│       │ 1. CREATE PAYMENT                        │               │
│       │ (Debit buyer)                            │               │
│       ▼                                          │               │
│  ┌──────────────┐                                │               │
│  │   ESCROW     │    2. ACCEPT                   │               │
│  │   (PENDING)  │───────────────────────────────►│               │
│  └──────────────┘    (Add to unconfirmed)        │               │
│       │                                          │               │
│       │                                          │               │
│       │ 3. COMPLETE (with code)                  │               │
│       └─────────────────────────────────────────►│               │
│                     (Move to confirmed)          │               │
│                                                  │               │
└─────────────────────────────────────────────────────────────────┘

Wallet System

Balance Types

TypeDescriptionWithdrawable
balanceConfirmed, cleared funds✅ Yes
unconfirmedBalancePending in escrow❌ No

Wallet Model

typescript
interface Wallet {
  id: string;
  userId: string;
  balance: number;           // Confirmed balance
  unconfirmedBalance: number; // Escrow balance
  currency: string;          // Inherited from user's country
  isActive: boolean;
  lastTransactionAt: Date;
}

Wallet Service Operations

typescript
// Create wallet from user's country profile
const wallet = await WalletService.createWalletFromUserProfile(userId);

// Ensure user has wallet (create if needed)
const wallet = await WalletService.ensureUserHasWallet(userId);

// Update balance (with transaction logging)
await WalletService.updateBalance(
  walletId,
  100.00,      // amount
  'debit',     // type: 'credit' | 'debit'
  'Purchase payment',
  paymentId,   // reference
  'SECURE_PAYMENT'  // referenceType
);

// Update unconfirmed balance (escrow)
await WalletService.updateUnconfirmedBalance(
  walletId,
  100.00,
  'credit',
  'Payment accepted'
);

// Confirm balance (move from unconfirmed to confirmed)
await WalletService.confirmBalance(
  walletId,
  100.00,
  'Payment completed'
);

Secure Payment Flow

1. Payment Creation

When buyer initiates a payment:

typescript
const payment = await SecurePaymentService.createSecurePayment({
  buyerId: 'user_123',
  shopId: 'shop_456',
  amount: 500.00,
  description: 'iPhone 12 Pro',
  productId: 'prod_789',
  chatId: 'chat_abc'
});

// Result:
{
  paymentId: 'SP1703184000001234',  // Unique ID
  completionCode: '482916',         // 6-digit code (given to buyer)
  status: 'PENDING',
  amount: 500.00
}

What happens:

  1. Validate buyer exists
  2. Validate shop exists and is active
  3. Check buyer has sufficient balance
  4. Debit buyer's wallet (balance → escrow)
  5. Generate unique completion code
  6. Create SecurePayment record

2. Seller Accepts Payment

Seller reviews and accepts the payment:

typescript
await SecurePaymentService.acceptSecurePayment({
  paymentId: 'SP1703184000001234',
  userId: 'seller_id'  // Must be shop owner
});

// Status: PENDING → ACCEPTED

What happens:

  1. Verify payment is PENDING
  2. Verify user is shop owner
  3. Add amount to seller's unconfirmedBalance
  4. Update status to ACCEPTED

3. Payment Completion

After buyer receives goods, they provide the completion code to seller:

typescript
await SecurePaymentService.completeSecurePayment({
  completionCode: '482916',
  userId: 'seller_id'  // Seller enters the code
});

// Status: ACCEPTED → COMPLETED

What happens:

  1. Find payment by completion code
  2. Verify payment is ACCEPTED
  3. Verify user is shop owner
  4. Move amount from seller's unconfirmedBalance to balance
  5. Update status to COMPLETED

Alternative Flows

Seller Refuses Payment

If seller can't fulfill the order:

typescript
await SecurePaymentService.refuseSecurePayment({
  paymentId: 'SP1703184000001234',
  reason: 'Item out of stock',
  userId: 'seller_id'
});

// Status: PENDING → REFUSED
// Buyer wallet: Refunded

Buyer Disputes Payment

If there's an issue with the transaction:

typescript
await SecurePaymentService.disputeSecurePayment({
  paymentId: 'SP1703184000001234',
  reason: 'Item not as described',
  userId: 'buyer_id'  // or seller_id
});

// Status: ACCEPTED → DISPUTED

Dispute rules:

  • Buyer can dispute any PENDING or ACCEPTED payment
  • Seller can only dispute ACCEPTED payments (e.g., buyer won't provide code)
  • Admin must resolve disputes manually

Seller Cancels Payment

typescript
await SecurePaymentService.cancelSecurePayment({
  paymentId: 'SP1703184000001234',
  reason: 'Cannot complete transaction',
  userId: 'seller_id'
});

// Status: PENDING/ACCEPTED → CANCELLED
// Buyer wallet: Refunded

Payment Status Lifecycle

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

          ┌───────────────┼───────────────┐
          │               │               │
          ▼               ▼               ▼
    ┌──────────┐    ┌──────────┐    ┌──────────┐
    │ ACCEPTED │    │ REFUSED  │    │CANCELLED │
    └────┬─────┘    └──────────┘    └──────────┘

    ┌────┴────┐
    │         │
    ▼         ▼
┌──────────┐  ┌──────────┐
│COMPLETED │  │ DISPUTED │
└──────────┘  └────┬─────┘


              ┌──────────┐
              │CANCELLED │ (after resolution)
              └──────────┘

Transaction Logging

Every wallet operation creates a transaction record:

typescript
interface WalletTransaction {
  id: string;
  walletId: string;
  userId: string;
  type: 'CREDIT' | 'DEBIT';
  amount: number;
  balanceBefore: number;
  balanceAfter: number;
  description: string;
  reference: string;        // SecurePayment ID
  referenceType: WalletRefType;
  metadata: object;
}

enum WalletRefType {
  SECURE_PAYMENT,
  DEPOSIT,
  WITHDRAWAL,
  TRANSFER,
  REFUND
}

Example Transaction History

TypeAmountDescriptionReference
DEBIT-500Secure payment to TechShopSP170318...
CREDIT+500Secure payment refundSP170318...
CREDIT+100DepositDEP170318...

Payment Audit Trail

Every payment action is logged:

typescript
interface SecurePaymentLog {
  id: string;
  paymentId: string;
  action: 'created' | 'accepted' | 'refused' | 'completed' | 'disputed' | 'cancelled';
  userId: string;
  ipAddress: string;
  userAgent: string;
  metadata: object;
  createdAt: Date;
}

API Endpoints

Create Payment

http
POST /secure-payments
Authorization: Bearer <token>

{
  "shopId": "shop_456",
  "amount": 500.00,
  "description": "iPhone 12 Pro",
  "productId": "prod_789",
  "chatId": "chat_abc"
}

Accept Payment (Seller)

http
POST /secure-payments/:paymentId/accept
Authorization: Bearer <token>

Complete Payment (Seller enters code)

http
POST /secure-payments/complete
Authorization: Bearer <token>

{
  "completionCode": "482916"
}

Get Payment Details

http
GET /secure-payments/:paymentId
Authorization: Bearer <token>

Get User's Payments

http
GET /secure-payments?status=PENDING&limit=20
Authorization: Bearer <token>

Security Considerations

Completion Code

  • 6-digit numeric code (100000-999999)
  • Generated server-side with crypto randomness
  • Unique across all active payments
  • Only buyer knows the code
  • Only seller can use it to complete

Authorization

  • Only buyer can create payments
  • Only shop owner can accept/refuse/cancel
  • Both parties can dispute
  • Admin can resolve disputes

Transaction Atomicity

All financial operations use Prisma transactions:

typescript
await prisma.$transaction(async (tx) => {
  // Debit buyer
  await WalletService.updateBalance(buyerWalletId, amount, 'debit', ..., tx);
  
  // Create payment
  const payment = await tx.securePayment.create({ ... });
  
  // Log action
  await tx.securePaymentLog.create({ ... });
  
  return payment;
});

Wallet Balance API

http
GET /wallet/balance
Authorization: Bearer <token>

Response:
{
  "balance": 1000.00,
  "unconfirmedBalance": 500.00,
  "totalBalance": 1500.00,
  "currency": "SZL"
}
http
GET /wallet/transactions?limit=20&type=credit
Authorization: Bearer <token>

Response:
{
  "transactions": [...],
  "total": 45
}

One chat. Everything done.