Skip to content

Vendors Domain

Overview

The Vendors domain manages vendor profiles, onboarding, verification, and vendor-related business logic. This domain represents service providers who offer wedding services through the Wedissimo marketplace.

Module Location: modules/Vendors/ (to be created) Current Location: app/Models/Vendor.php (core app, to be migrated)

Domain Responsibilities

  • Vendor registration and onboarding
  • Profile management and updates
  • Vendor verification and approval workflow
  • Vendor status management (active, suspended, pending)
  • Vendor categories and service offerings
  • Business information and contact details
  • Operating hours and service areas
  • Vendor search and discovery (integration with Search domain)

Core Entities

Vendor

Primary Entity: Represents a wedding service provider

Key Attributes:

  • id (UUID)
  • user_id (FK to User)
  • business_name
  • description
  • location, city, latitude, longitude
  • status (active, pending, suspended)
  • approval_status (approved, pending, rejected)
  • verified (boolean)
  • years_of_experience
  • rating, review_count

Model Location: app/Models/Vendor.php

Vendor Status

Value object representing vendor operational status:

  • active - Vendor is live and accepting business
  • pending - Awaiting approval or verification
  • suspended - Temporarily disabled
  • inactive - Vendor has deactivated their account

Approval Status

Separate approval workflow status:

  • approved - Verified by administrators
  • pending - Awaiting review
  • rejected - Did not meet criteria

Domain Services

VendorService (to be created)

Responsibilities:

  • Create and update vendor profiles
  • Handle verification workflow
  • Manage vendor status transitions
  • Coordinate with other domains (Search, Media, Communications)

Key Methods:

php
class VendorService
{
    public function createVendor(User $user, array $data): Vendor;
    public function updateProfile(Vendor $vendor, array $data): Vendor;
    public function requestVerification(Vendor $vendor): void;
    public function approveVendor(Vendor $vendor, User $admin): void;
    public function rejectVendor(Vendor $vendor, string $reason): void;
    public function suspendVendor(Vendor $vendor, string $reason): void;
    public function reactivateVendor(Vendor $vendor): void;
    public function updateOperatingHours(Vendor $vendor, array $hours): void;
}

Domain Events

VendorCreated

Dispatched when a new vendor registers.

Listeners:

  • Send welcome email (Communications domain)
  • Create default settings
  • Notify administrators

VendorApproved

Dispatched when vendor is verified and approved.

Listeners:

  • Send approval notification (Communications domain)
  • Index in search engine (Search domain)
  • Grant marketplace access

VendorSuspended

Dispatched when vendor account is suspended.

Listeners:

  • Send suspension notification (Communications domain)
  • Delist from search (Search domain)
  • Notify active bookings (Bookings domain)

VendorProfileUpdated

Dispatched when profile information changes.

Listeners:

  • Re-index in search (Search domain)
  • Update cached data

API Endpoints

Public Endpoints

GET    /api/v1/vendors/search          # Search vendors (Typesense with filters)
GET    /api/v1/vendors/{idorslug}            # Get vendor details by UUID (primary) or slug
GET    /api/v1/vendors/{idorslug}/listings            # Get vendor listings
GET    /api/v1/service-types           # List all service types (categories)
GET    /api/v1/service-types/{slug}    # Get service type details with vendors

Endpoint Usage Notes:

  • By ID (/vendors/{id}): Use when you have the UUID from API responses or internal references. IDs are permanent and never change.
  • By Slug (/vendors-by-slug/{slug}): Use for SEO-friendly public pages and shareable URLs. Slugs may change if vendor renames their business.

Authentication

POST   /api/v1/vendor/register # Register as vendor (see Users domain)

Authenticated Vendor Endpoints

Profile Management

GET    /api/v1/vendors/me           # Get authenticated vendor profile
PATCH  /api/v1/vendors/me           # Update vendor profile (partial)

Onboarding

GET    /api/v1/vendors/me/onboarding              # Get onboarding progress
PUT    /api/v1/vendors/me/onboarding/step/{step}  # Complete onboarding step
POST   /api/v1/vendors/me/onboarding/submit       # Submit for approval
POST   /api/v1/vendors/me/gallery          # Upload gallery images
DELETE /api/v1/vendors/me/gallery/{mediaId} # Delete gallery image

Database Schema

vendors table

sql
CREATE TABLE vendors (
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES users(id),
    business_name VARCHAR(255) NOT NULL,
    description TEXT,
    location VARCHAR(255),
    city VARCHAR(100),
    latitude DECIMAL(10, 8),
    longitude DECIMAL(11, 8),
    status VARCHAR(50) DEFAULT 'pending',
    approval_status VARCHAR(50) DEFAULT 'pending',
    verified BOOLEAN DEFAULT FALSE,
    years_of_experience INTEGER DEFAULT 0,
    rating DECIMAL(3, 2) DEFAULT 0.00,
    review_count INTEGER DEFAULT 0,
    wp_id INTEGER,  -- WordPress migration tracking
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    approved_at TIMESTAMP,
    UNIQUE(user_id)
);

CREATE INDEX idx_vendors_status ON vendors(status);
CREATE INDEX idx_vendors_approval ON vendors(approval_status);
CREATE INDEX idx_vendors_verified ON vendors(verified);
CREATE INDEX idx_vendors_location ON vendors(latitude, longitude);

vendor_categories (pivot)

Links vendors to categories (many-to-many).

Relationships

Belongs To

  • User - Each vendor has one user account

Has Many

  • Listings - Vendor can have multiple service listings
  • Reviews - Vendor receives reviews
  • Bookings - Vendor receives booking requests

Many to Many

  • Categories - Vendor operates in multiple service categories
  • Tags - Vendor has searchable tags
  • Media - Vendor has profile images, portfolio

Domain Rules & Invariants

  1. One Vendor Per User: A user can only create one vendor profile
  2. Verified Vendors Only: Only verified vendors appear in public search
  3. Active Status Required: Vendor must be active to receive bookings
  4. Location Required: Vendors must provide service location
  5. Category Minimum: Vendor must select at least one service category

Integration Points

Search Domain

  • Vendor profiles are indexed in Typesense
  • Search includes: name, business_name, description, location, city
  • Facets: categories, verified, approval_status, years_of_experience

Media Domain

  • Profile photos
  • Business logos
  • Portfolio images
  • Certification documents

Communications Domain

  • Welcome emails on registration
  • Approval/rejection notifications
  • Booking notifications
  • Review notifications

Listings Domain

  • Vendors create and manage listings
  • Listing visibility tied to vendor status

Migration Plan

Current State: Vendor logic in core app/ directory

Target State: Isolated Vendors module at modules/Vendors/

Migration Steps

  1. Create module structure at modules/Vendors/
  2. Move app/Models/Vendor.php to modules/Vendors/Models/Vendor.php
  3. Create VendorService with business logic extracted from controllers
  4. Move controllers to modules/Vendors/Http/Controllers/
  5. Create domain events (VendorCreated, VendorApproved, etc.)
  6. Move routes to modules/Vendors/Routes/api.php
  7. Create module-specific migrations in modules/Vendors/Database/Migrations/
  8. Move tests to modules/Vendors/Tests/
  9. Update namespaces and imports throughout codebase
  10. Register module in config/platform.common.modules

Testing Strategy

Unit Tests

  • Vendor model behavior
  • Status transitions
  • Validation rules
  • Value objects

Feature Tests

  • Vendor registration flow
  • Profile updates
  • Verification workflow
  • Admin approval/rejection
  • Search integration

Example Tests

php
test('vendor can view their profile', function () {
    $vendor = Vendor::factory()->create();
    $user = $vendor->user;

    $response = $this->actingAs($user)
        ->getJson('/api/v1/vendors/me');

    $response->assertStatus(200)
        ->assertJsonStructure(['id', 'business_name', 'slug', 'status']);
});

test('vendor can update their profile', function () {
    $vendor = Vendor::factory()->create();
    $user = $vendor->user;

    $response = $this->actingAs($user)
        ->patchJson('/api/v1/vendors/me', [
            'description' => 'Award-winning wedding photographer',
            'city' => 'Manchester',
        ]);

    $response->assertStatus(200);
    expect($vendor->refresh()->city)->toBe('Manchester');
});

test('public can search vendors', function () {
    Vendor::factory()->count(5)->create(['status' => 'active', 'verified' => true]);

    $response = $this->getJson('/api/v1/vendors/search?query=photographer&verified=true');

    $response->assertStatus(200)
        ->assertJsonStructure(['data', 'meta']);
});

test('public can view vendor by id', function () {
    $vendor = Vendor::factory()->create();

    $response = $this->getJson("/api/v1/vendors/{$vendor->id}");

    $response->assertStatus(200)
        ->assertJson(['id' => $vendor->id]);
});

test('public can view vendor by slug', function () {
    $vendor = Vendor::factory()->create(['slug' => 'james-smith-photography']);

    $response = $this->getJson('/api/v1/vendors/slug/james-smith-photography');
    $response = $this->getJson('/api/v1/vendors/slug/james-smith-photography');

    $response->assertStatus(200)
        ->assertJson(['slug' => 'james-smith-photography']);
});

Future Enhancements

  • Vendor subscription tiers (Basic, Premium, Enterprise)
  • Vendor analytics dashboard
  • Automated verification via document scanning
  • Vendor reputation scoring
  • Multi-location support for vendor chains
  • Vendor team member management
  • Integration with third-party background check services

Wedissimo API Documentation