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_namedescriptionlocation,city,latitude,longitudestatus(active, pending, suspended)approval_status(approved, pending, rejected)verified(boolean)years_of_experiencerating,review_count
Model Location: app/Models/Vendor.php
Vendor Status
Value object representing vendor operational status:
active- Vendor is live and accepting businesspending- Awaiting approval or verificationsuspended- Temporarily disabledinactive- Vendor has deactivated their account
Approval Status
Separate approval workflow status:
approved- Verified by administratorspending- Awaiting reviewrejected- 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:
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 vendorsEndpoint 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 approvalGallery Management
POST /api/v1/vendors/me/gallery # Upload gallery images
DELETE /api/v1/vendors/me/gallery/{mediaId} # Delete gallery imageDatabase Schema
vendors table
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 listingsReviews- Vendor receives reviewsBookings- Vendor receives booking requests
Many to Many
Categories- Vendor operates in multiple service categoriesTags- Vendor has searchable tagsMedia- Vendor has profile images, portfolio
Domain Rules & Invariants
- One Vendor Per User: A user can only create one vendor profile
- Verified Vendors Only: Only verified vendors appear in public search
- Active Status Required: Vendor must be active to receive bookings
- Location Required: Vendors must provide service location
- 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
- Create module structure at
modules/Vendors/ - Move
app/Models/Vendor.phptomodules/Vendors/Models/Vendor.php - Create
VendorServicewith business logic extracted from controllers - Move controllers to
modules/Vendors/Http/Controllers/ - Create domain events (VendorCreated, VendorApproved, etc.)
- Move routes to
modules/Vendors/Routes/api.php - Create module-specific migrations in
modules/Vendors/Database/Migrations/ - Move tests to
modules/Vendors/Tests/ - Update namespaces and imports throughout codebase
- 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
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