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.