Skip to content

YeboShops Services

Complete documentation of every service in the YeboShops API.

Service Overview

ServicePurposeKey Methods
SecurePaymentServiceEscrow payment processingcreate, accept, refuse, complete, dispute
WalletServiceUser wallet managementcreateWallet, updateBalance, confirmBalance
EmbeddingServiceVector search with pgvectorgenerateEmbeddings, searchProducts
SearchServiceAI-powered product searchsearch, expandQuery, getTrending
ChatServiceBuyer-seller messagingcreateMessage, getMessages, getUserChats
ProductServiceProduct CRUD & AI processingcreate, update, processWebhook
ShopServiceShop managementcreate, update, getProducts
NotificationServicePush notificationscreateNotification, markRead

SecurePaymentService

The escrow payment service handles the complete lifecycle of P2P transactions.

Payment Flow

┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│ PENDING │────►│ACCEPTED │────►│COMPLETED│     │CANCELLED│
└─────────┘     └─────────┘     └─────────┘     └─────────┘
     │               │                               ▲
     │               │                               │
     ▼               ▼                               │
┌─────────┐     ┌─────────┐                         │
│ REFUSED │     │DISPUTED │─────────────────────────┘
└─────────┘     └─────────┘

Key Methods

typescript
// Create a secure payment (debits buyer's wallet)
static async createSecurePayment(data: CreateSecurePaymentRequest): Promise<SecurePayment> {
  // 1. Validate buyer exists
  // 2. Validate shop exists and is active
  // 3. Check buyer's wallet balance
  // 4. Debit buyer's wallet
  // 5. Generate unique completion code (6 digits)
  // 6. Generate unique payment ID (SP + timestamp + random)
  // 7. Create SecurePayment record with PENDING status
  // 8. Log payment action for audit trail
}

// Shop owner accepts payment (adds to unconfirmed balance)
static async acceptSecurePayment(data: AcceptSecurePaymentRequest): Promise<SecurePayment> {
  // 1. Find payment by paymentId
  // 2. Verify payment is PENDING
  // 3. Verify user is shop owner
  // 4. Add amount to shop owner's unconfirmed balance
  // 5. Update status to ACCEPTED
}

// Complete payment using completion code (confirms balance)
static async completeSecurePayment(data: CompleteSecurePaymentRequest): Promise<SecurePayment> {
  // 1. Find payment by completion code
  // 2. Verify payment is ACCEPTED (not PENDING)
  // 3. Verify user is shop owner
  // 4. Move amount from unconfirmed to confirmed balance
  // 5. Update status to COMPLETED
}

// Refuse payment (refunds buyer)
static async refuseSecurePayment(data: RefuseSecurePaymentRequest): Promise<SecurePayment> {
  // 1. Find payment by paymentId
  // 2. Verify payment is PENDING
  // 3. Verify user is shop owner
  // 4. Refund buyer's wallet
  // 5. Update status to REFUSED
}

Payment States

StatusDescriptionNext States
PENDINGPayment created, awaiting seller responseACCEPTED, REFUSED
ACCEPTEDSeller accepted, funds in escrowCOMPLETED, DISPUTED
REFUSEDSeller refused, buyer refunded
COMPLETEDTransaction finalized
DISPUTEDUnder dispute reviewCANCELLED
CANCELLEDCancelled, funds returned

WalletService

Manages user wallets with confirmed and unconfirmed balances.

Key Methods

typescript
// Create wallet from user's country profile
static async createWalletFromUserProfile(userId: string): Promise<Wallet> {
  const user = await db.user.findUnique({ include: { country: true } });
  const currency = user.country?.currency?.code;
  return this.createWalletForUser(userId, currency);
}

// Ensure user has a wallet (create if doesn't exist)
static async ensureUserHasWallet(userId: string): Promise<Wallet> {
  const existing = await db.wallet.findFirst({ where: { userId, isActive: true } });
  if (existing) return existing;
  return this.createWalletFromUserProfile(userId);
}

// Update balance with transaction logging
static async updateBalance(
  walletId: string,
  amount: number,
  type: 'credit' | 'debit',
  description?: string,
  reference?: string,
  referenceType?: WalletRefType
): Promise<Wallet> {
  // Calculate new balance
  // Verify sufficient funds for debit
  // Update wallet balance
  // Create WalletTransaction record
}

// Confirm balance (move from unconfirmed to confirmed)
static async confirmBalance(walletId: string, amount: number): Promise<Wallet> {
  // Verify sufficient unconfirmed balance
  // Add to confirmed balance
  // Subtract from unconfirmed balance
  // Create transaction record
}

Balance Types

TypePurpose
balanceConfirmed, withdrawable funds
unconfirmedBalancePending transactions (escrow)

EmbeddingService

Handles vector embeddings using Voyage AI and pgvector for semantic search.

Configuration

typescript
const VOYAGE_API_KEY = process.env.VOYAGE_API_KEY;
const VOYAGE_MODEL = 'voyage-3-lite';  // 512 dimensions
const BATCH_SIZE = 50;

Key Methods

typescript
// Generate embeddings via Voyage AI
static async generateEmbeddings(texts: string[]): Promise<number[][]> {
  const response = await fetch('https://api.voyageai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${VOYAGE_API_KEY}`,
    },
    body: JSON.stringify({
      input: texts,
      model: VOYAGE_MODEL,
    }),
  });
  return result.data.map(item => item.embedding);
}

// Create embedding text for a product
static createEmbeddingText(product: any): string {
  return [
    product.title,
    product.description,
    product.category,
    product.tags?.join(' '),
    product.condition,
    product.color?.join(' ')
  ].filter(Boolean).join(' ');
}

// Search products using vector similarity
static async searchProducts(
  queryEmbedding: number[],
  filters: SearchFilters,
  limit: number = 20
): Promise<any[]> {
  // Raw SQL with pgvector cosine distance
  const results = await prisma.$queryRawUnsafe(`
    SELECT 
      p.id, p.title, p.slug, p.category,
      (p.embedding::vector <=> $1::vector) as distance,
      (1 - (p.embedding::vector <=> $1::vector)) as score
    FROM "Product" p
    WHERE p.status = 'PUBLISHED' 
      AND p."isActive" = true 
      AND p.embedding IS NOT NULL
    ORDER BY p.embedding::vector <=> $1::vector
    LIMIT $2
  `, JSON.stringify(queryEmbedding), limit);
  return results;
}

// Find similar products
static async findSimilarProducts(productId: string, limit: number = 10): Promise<any[]> {
  // Uses vector similarity between products
}

Vector Index

sql
-- HNSW index for fast cosine similarity search
CREATE INDEX product_embedding_cosine_idx 
ON "Product" USING hnsw (embedding vector_cosine_ops) 
WITH (m = 16, ef_construction = 64);

SearchService

Combines AI query expansion with vector search for intelligent product discovery.

Key Methods

typescript
// Expand natural language query using Gemini
static async expandQuery(query: string): Promise<SearchIntent> {
  const prompt = `Analyze this search query for an African classifieds marketplace...`;
  
  const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent`, {
    body: JSON.stringify({
      contents: [{ parts: [{ text: prompt }] }],
      generationConfig: {
        temperature: 0.1,
        responseMimeType: 'application/json'
      }
    })
  });
  
  return {
    keywords: ['blue', 'sneakers', 'shoes'],
    categories: ['Fashion & Apparel'],
    priceRange: { max: 50 },
    condition: null,
    intent: 'looking for affordable blue sneakers',
    confidence: 0.9
  };
}

// Vector-first search with keyword fallback
static async search(query: string, filters: SearchFilters): Promise<SearchResponse> {
  // 1. Generate query embedding
  const [queryEmbedding] = await EmbeddingService.generateEmbeddings([query]);
  
  // 2. Search using vector similarity
  let vectorResults = await EmbeddingService.searchProducts(queryEmbedding, filters);
  
  // 3. If insufficient results, add keyword fallback
  if (vectorResults.length < 3) {
    const keywordProducts = await prisma.product.findMany({
      where: {
        OR: [
          { title: { contains: query, mode: 'insensitive' } },
          { description: { contains: query, mode: 'insensitive' } },
          { tags: { hasSome: keywords } }
        ]
      }
    });
    results.push(...keywordProducts);
  }
  
  return { results, totalCount, query, searchTime };
}

ChatService

Handles buyer-seller messaging with product context awareness.

Key Methods

typescript
// Create a new message
static async createMessage(data: CreateMessageData, userId: string): Promise<CreateMessageResponse> {
  // 1. Find or create chat
  // 2. Handle product context (extract from message or inherit from chat)
  // 3. Determine sender type (USER or SHOP)
  // 4. Create message with attachments
  // 5. Create notification for recipient
  // 6. Generate suggested actions
}

// Get messages with enhanced pagination
static async getMessages(chatId: string, query: GetMessagesQuery): Promise<GetMessagesResponse> {
  // Cursor-based pagination with 'before' and 'after'
  // Pre-fetch all senders for efficiency
  // Enhance messages with computed data
}

// Get user's chats with unread counts
static async getUserChatsWithUnreadCounts(userId: string): Promise<{
  chats: EnrichedChat[];
  pagination: { nextCursor: string | null; hasMore: boolean };
}> {
  // Aggregate unread counts
  // Get latest messages and product contexts
  // Determine recipient based on viewer's perspective
}

Message Types

typescript
enum MessageType {
  TEXT           // Regular text message
  PRODUCT_SHARE  // Sharing a product into the chat
  PRODUCT_INQUIRY // Initial inquiry about a product
  IMAGE          // Image attachment
  SYSTEM         // System messages
}

ProductService

Manages product lifecycle including AI-powered processing.

Key Methods

typescript
// Create a product
async function createProduct(params: CreateProductParams) {
  // 1. Validate shop ownership
  // 2. Generate unique slug
  // 3. Create product in DRAFT status
  // 4. Create ProductStats record
}

// Process AI webhook data
async function processProductMediaWebhook(params: { productId: string; processedData: any }) {
  // 1. Map webhook data to product fields
  // 2. Update title, description, category, tags
  // 3. Mark AI processing as completed
  // 4. Change status from DRAFT to PUBLISHED
  // 5. Generate and store embedding
}

// Get products for feed
async function getFeedProducts(query: any, userId?: string) {
  // Filter by status: PUBLISHED, isActive: true
  // Order by viewCount, favoriteCount, createdAt
  // Return with sanitized data for frontend
}

Product Status Flow

DRAFT ──► PUBLISHED ──► ARCHIVED


         REJECTED

One chat. Everything done.