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.