Engine
Laravel 13 / PHP 8.3Blade + Tailwind 4Modern monolithic marketplace engine with Tailwind CSS and Alpine.js.
Storefront, Vendor Portal, and Superadmin Dashboard in one repository.
Stripe, PayPal, Razorpay, Flutterwave, Mollie, Cash On Delivery & Wallet.
Visual translation manager, dynamic currency switch, and geo-taxes.
Mono. Marketplace.
Multi-vendor commerce, one Laravel application.
Complete documentation for the MONO Marketplace web product: the themeable storefront, the vendor dashboard, the admin panel, and the Laravel services behind all three. Install it, run the marketplace, and extend it.
Surface
3 panels / 9 themes14 payment methodsScale
94 tables / 78 models32 permissions01 / Overview
One codebase, three front doors
MONO Marketplace is a multi-vendor commerce platform. A single Laravel application serves three completely separate front doors, and they all share one database, one permission system, and one theme engine.
Customer website
- Themeable catalog with nine shipped storefront themes.
- Cart, coupons, vouchers, wishlist, loyalty, and wallet.
- Fourteen payment methods including cash on delivery and bank transfer.
- Digital goods, bundles, and vendor chat with realtime delivery.
Seller dashboard
- Applications, product, bundle, and taxonomy management.
- Order fulfilment with per-item digital delivery.
- Settlements, withdrawal requests, subscription packages.
- AI suite with metered text, translation, and image quotas.
Platform control
- Orders, invoices, refunds, and a full finance ledger.
- Vendor approvals, subscriptions, payouts, and delivery operations.
- Roles, permissions, languages, media, and site settings.
- Push broadcasting, newsletters, and an AI administration toolset.
What ships in the package
Nine themes on disk: mono (built in), plus beauty, electronics, fashion, fullwidth, grocery, home, petshop, and sports. Each is a self-contained package with its own views, assets, palette, and optional database tables.
Two Flutter clients ship alongside: the customer app and the vendor app. Both talk to the same versioned API described in this document.
public/install.php is a self-contained wizard that creates the database, writes the environment file, creates the first administrator, and activates a theme. No Composer, Node, or shell access is required on the host.
The three front doors, at a glance
Each interface is purpose-built for desktop web workflows — responsive Blade templates styled with Tailwind CSS, with zero client-side compilation steps.
Who this documentation is for
This guide covers the web product: the storefront, the vendor panel, the admin panel, and the backend services that support them. The two Flutter applications have their own companion documents. Everything here is written against the shipped source, so route names, table names, and permission keys match what you will find in the package.
https://your-domain.com/api/v102 / Overview
Stack, panels, and how a request flows
Technology stack
| Layer | Choice | Notes |
|---|---|---|
| Language | PHP ^8.3 | Platform is pinned to 8.3.30. |
| Framework | Laravel ^13.8 | Blade templates, no SPA build step. |
| API auth | Laravel Sanctum ^4.3 | Bearer personal access tokens. |
| Realtime chat | Ably ^1.1 | Server-minted short-lived client tokens. |
| PDF invoices | dompdf ^3.1 | Renders and emails invoice documents. |
| Machine translation | google-translate-php ^5.3 | Backs the admin AI translation tools. |
| CSS | Tailwind CSS ^4.3 | CSS-first config via an @theme block. No Vite, no bundler. |
| Databases | MySQL or SQLite | Both drivers are supported; MySQL is the production target. |
Assets are plain Blade, plain CSS compiled by the Tailwind CLI, and plain
JavaScript served from public/js. If you add a JavaScript entry
point, add the file to public/js and reference it directly; do
not look for vite.config.js or an npm run dev asset
server.
The three panels
Storefront
- Public routes in
routes/web.php. - Views resolve through
ThemeServiceProvider, which prepends the active theme's view path so theme templates win over the default ones. - A theme that does not supply a view falls back to
resources/viewsautomatically. - Uploaded media resolves through
PublicMedia::url().
Vendor panel
- Routes under
prefix('dashboard')withauth:vendor,vendor.verified, andvendor.approved. - Layout is
layouts/dashboard.blade.php. - Signed app-bridge routes let the Flutter vendor app open specific panel pages.
Admin panel
- Routes under
prefix('admin')withauth:admin. - Layout is
layouts/admin.blade.phpand owns the entire sidebar navigation map. - Every page is additionally guarded by a
permission:middleware key.
Request lifecycle
The API stack prepends ForceJsonResponse and
SetApiLocale; the web stack appends SetLocale.
Successful API responses use a { success, data, meta } envelope.
Errors are rendered by App\Support\ApiException into
{ success: false, error: { message, code, fields } }, so the
mobile clients only ever parse one shape.
Guest redirects are area aware: an unauthenticated admin/*
request lands on admin.login, a vendor or
dashboard request lands on vendor.login, and
everything else lands on the customer login route.
Custom middleware
| Alias | Class | What it enforces |
|---|---|---|
permission | HasPermission | Admin permission check. Pipe-separated values mean "any of". |
auth.optional | OptionalSanctumAuth | Resolves the user when a token is present but never rejects, so public catalog routes can resolve wishlist flags. |
vendor.verified | EnsureVendorEmailVerified | Vendor email must be verified. |
vendor.approved | EnsureVendorApproved | Vendor registration must be approved. |
guest.checkout | EnsureGuestCheckoutAllowed | Honours the guest-checkout setting. |
03 / Overview
Installation on shared hosting and on a dev machine
MONO installs two ways. The web installer is the supported path for shared hosting, where you have a control panel but no shell. The Artisan path is for developers and CI. The walkthrough below was captured on a cPanel account; other control panels follow the same shape.
Complete Guide to Installing and Setting Up the Mono Market Website and Backend on cPanel
Follow the complete real-time video walkthrough by ClotCode demonstrating every step from blank cPanel hosting to a fully running multivendor marketplace: MySQL database configuration, File Manager upload & archive extraction, running the 5-step interactive web wizard, PHP 8.3 & extension checks, automated background cron scheduling, and live storefront verification.
You need the release archive mono-marketplace-installer.zip
(built on your machine with python tools/build_release.py — it
already contains vendor/, the compiled CSS, and the public tree,
so the host needs no Composer, Node, or shell access), a cPanel or similar
hosting account with MySQL, and PHP 8.3 or newer. Every screenshot below is
clickable for a full-size view.
Part 1 — Create the database in cPanel
- Open the database manager.
In cPanel, scroll to the Databases section and open Manage My Databases.
cPanel → Databases → Manage My Databases - Create the database.
Under Create New Database, type a name after your account prefix and click Create Database. cPanel prepends the account prefix —
monobecomes something likeclotcode_mono. Note the full name; the installer will ask for it exactly.Create New Database — note the prefixed name - Create the database user.
Scroll to Database Users → Add New User, pick a username, and generate a strong password. Note the username and password too, then click Create User.
Database Users → Add New User → Create User - Attach the user to the database.
In Add User To Database, select the user and the database you just created, and click Add.
Add User To Database → Add - Grant all privileges.
cPanel opens the privilege screen for that user/database pair. Tick ALL PRIVILEGES, then click Make Changes to save. The installer migrates and seeds through this user, so it needs full rights on this database.
ALL PRIVILEGES → Make Changes
Part 2 — Upload and extract the installer
- Open File Manager.
In cPanel, open Files → File Manager and enter the document root of the domain you are installing on —
public_htmlfor the primary domain, or the subfolder the domain points at.cPanel → Files → File Manager → public_html - Upload the archive.
Click Upload and select
mono-marketplace-installer.zip. Stay on the page until the progress bar reaches 100%.Upload mono-marketplace-installer.zip - Extract it in place.
Back in File Manager, select the zip, click Extract, keep the destination path
/public_html, and click Extract Files. The archive expands into the application tree; delete the zip afterwards to reclaim the space.Extract into /public_html
Part 3 — Run the installer wizard
Visit https://your-domain.com/install.php. If
.env is still missing, public/index.php also
redirects the storefront's first request there automatically. The wizard is
five steps and takes about a minute.
- Welcome — server requirements.
The installer checks PHP 8.3+, every required extension, the presence of
vendor/, and thatstorage/,bootstrap/cache/, and.envare writable. When each row reads Ready, continue to the database step.install.php — step 1 / 5 — server requirements - Database connection.
Keep the type on MySQL / MariaDB, leave the host on
localhostand the port on3306unless your host says otherwise, and enter the database name, username, and password from Part 1. Test connection & create tables verifies the login, writes.env, and runs the migrations. SQLite is also accepted for small or staging installs.Step 2 / 5 — database connection & tables - Site & admin account.
The site URL is auto-detected — change it only if the store will be served from another address or a subfolder. Set the store name and tagline, then create the first administrator; the account is attached to the
super_adminrole automatically. Leave Install the current catalog demo ticked if you want starter products, categories, brands, vendors, and bundles to explore.Step 3 / 5 — site & admin account - Pick a theme.
Choose one of the nine storefront themes. The installer runs that theme's migrations and copies its assets. You can switch or add themes later in the admin panel under Appearance → Themes.
Step 4 / 5 — pick a theme - Finish.
The installer writes
storage/app/installed.lockand shows your admin login and URLs. Log in to the Admin Panel → signs you in with a one-time token that deletes itself on first use.Step 5 / 5 — installation complete
1. Delete public/install.php immediately from the server via cPanel File Manager. Until you do, anyone navigating to the URL could re-trigger configuration.
2. Register the Laravel scheduler in cPanel Cron Jobs so order status workflows, coupon expirations, payout calculations, and SMS queues execute on time.
cPanel Server Environment & PHP Configuration
To ensure peak performance and seamless file uploads on cPanel, configure your PHP environment according to the specifications below:
| Directive | Recommended Value | Purpose |
|---|---|---|
| PHP Version | PHP 8.3 or PHP 8.4 | Select in cPanel → MultiPHP Manager. |
| memory_limit | 256M or 512M | Required for processing catalog imports, image resizing, and PDF invoices. |
| upload_max_filesize | 64M | Supports product images, downloadable digital archives, and vendor attachments. |
| post_max_size | 64M | Must match or exceed upload_max_filesize. |
| max_execution_time | 300 | Allows ample execution time for multi-vendor batch migrations and catalog seeds. |
| max_input_vars | 3000 | Required for large multi-attribute product matrices and dynamic nested permissions. |
cPanel Cron Jobs Configuration
In cPanel, open Advanced → Cron Jobs. Set the schedule to Once Per Minute (* * * * *), and enter the command line matching your hosting account user path:
* * * * * /usr/local/bin/php /home/username/public_html/artisan schedule:run >> /dev/null 2>&1
Note: Replace username with your actual cPanel username. If your host uses a versioned PHP CLI path, specify /usr/local/bin/ea-php83 instead of /usr/local/bin/php.
Background Queue Processing on Shared Hosting
If your shared hosting plan does not permit daemonized Supervisor processes, process your background job queues (order confirmation emails, push notifications, and gateway status checks) by configuring a secondary cron job running every 2 to 3 minutes:
*/2 * * * * /usr/local/bin/php /home/username/public_html/artisan queue:work --stop-when-empty --tries=3 >> /dev/null 2>&1
Email (SMTP) Configuration
Configure outgoing email delivery directly via the Admin panel under Settings → Mail / SMTP, or by adjusting your server's .env file:
MAIL_MAILER=smtp
MAIL_HOST=mail.yourdomain.com
MAIL_PORT=465
MAIL_USERNAME=noreply@yourdomain.com
MAIL_PASSWORD=your_secure_email_password
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=noreply@yourdomain.com
MAIL_FROM_NAME="Mono Marketplace"
Host requirements
pdo_mysql or pdo_sqlite, mbstring, openssl, tokenizer, xml, ctype, json, fileinfo, gd, zip, intl.
Composer, Node, npm, and shell access are all unnecessary on the host. Everything the runtime needs is already in the release archive.
storage/, bootstrap/cache/, and public/storage must be writable. public/storage is a symlink to storage/app/public, which is where every upload lands.
Developer install
composer installphp artisan key:generatephp artisan migrate --seedphp artisan storage:linkphp artisan servenpm run build:cssAfter migrate --seed the demo data creates an administrator at
admin@mono.test with the password password, a
customer at jordan@example.com, and eight vendors whose passwords
are also password. Change or remove all of these before the site
is reachable by anyone but you.
04 / Overview
Updates, scheduled jobs, and the queue worker
Applying an update
- Back up first.
Copy the database and the
storage/directory. Uploads and the environment file both live outside version control. - Replace the code.
Upload the new release archive and extract it over the existing files. Do not delete
storage/,.env, orpublic/storageduring extraction. - Migrate.
Run
php artisan migrate. Theme migrations are registered automatically byThemeServiceProvider, somigratepicks up tables declared by the active theme. - Rebuild CSS if you customised it.
Run
npm run build:cssonly if you editedresources/css/app.cssor a theme stylesheet. - Clear caches.
php artisan config:clear,php artisan cache:clear, andphp artisan view:clearare enough after a migration run.
MONO is updated by replacing files and migrating. It deliberately does not phone home or self-modify, which is what makes it safe to run on a locked-down shared host.
Scheduled jobs
Background work is data-driven rather than hard-coded in
routes/console.php. The scheduled_jobs table holds
each job's key, Artisan command, frequency, and enabled flag, and
ScheduledJobService registers them with Laravel's scheduler at
boot. Admins edit the table at
/admin/settings/scheduled-jobs and can run any job on demand.
| Frequency | Seeded jobs |
|---|---|
| Every minute | orders:cancel-expired, ai-credits:cancel-expired |
| Every five minutes | featured:expire |
| Daily | vendor-packages:notify-expiry, loyalty:expire-points, badges:expire-new |
For these to run, the host's cron must hit
/<path>/artisan schedule:run every minute. On shared hosting
that is usually a cron entry in the control panel rather than a real crontab.
* * * * * cd /path/to/mono && php artisan schedule:run >> /dev/null 2>&1Queue worker
Three jobs send mail or push notifications: newsletter campaigns, customer
push, and vendor push. A long newsletter campaign sets
timeout = 1800 precisely because it chunks through subscribers in
a single worker run. If you leave notifications and newsletters enabled, run a
worker.
php artisan queue:work --tries=2queue-worker.batWithout a worker the jobs simply sit in the jobs table. Orders,
payments, and the admin panel all continue to work; only asynchronous email
and push are delayed.
05 / Storefront
Themes and the theme engine
A theme is a folder under themes/ that contains a
theme.json manifest. The filesystem is the source of truth: a
theme exists because its folder exists, and removing the folder removes it
from the admin panel.
Every theme below was activated live and captured from its home page at a desktop viewport, so what you see is what each theme actually ships — hero, palette, typography, and storefront sections.
Switching themes is instant and reversible — the admin panel activates a theme by writing one setting; no rebuild, no downtime. Captures above were taken by activating each theme in turn on the same demo catalog.
| Slug | Name | Own tables |
|---|---|---|
mono | Mono (built in) | None. The manifest lives at resources/theme/mono.json, outside the removable tree. |
beauty | Beauty & Wellness | No extra tables. |
electronics | Electronics | Yes, electronics_features. |
fashion | Fashion | Yes, fashion_looks. |
fullwidth | Mono Edge | No extra tables. |
grocery | Grocery | Yes, grocery_promos. |
home | Home & Living | No extra tables. |
petshop | Pet Shop | Yes, petshop_banners. |
sports | Sports | Yes, sports_banners. |
config/themes.php is stale.
That file still lists the original eight and omits fullwidth.
ThemeManager is the real source of truth; trust the
themes/ directory, not the config file.
How view resolution works
ThemeServiceProvider registers
resources/views/core under the core:: namespace and
calls prependLocation() with the active theme's views path. That
single call is what makes theming work: a theme view shadows the default view
of the same name, and anything the theme does not provide falls through to
the core views. The provider also registers the active theme's
migrations/ path with the migrator.
Theme manifests
Each theme.json declares the slug, name, description, version,
owned tables[], a full palette, and an
assets block (CSS path, Google Fonts URL, body class). The palette
carries per-colour labels, defaults, and CSS variable names, shape radii, an
isDark flag, status colours for the seven order statuses, and logo
and invoice accents.
Removing a theme drops every table declared in its manifest. Take a
database backup first. This is why mono is deliberately kept
outside themes/: it cannot be uninstalled out from under the
site.
App themes are a separate axis
AppThemeRegistry controls the mobile app's visual
system, not the website. It offers standard,
marketplace, aurora, and nova, with
fresh-market as the default. The registry keeps an integer
version stamp in active_app_theme_version that devices diff
against so the apps know when to re-fetch. Colours always come from the
active storefront palette, so the app and the site stay visually consistent.
06 / Storefront
Products, variations, and taxonomy
Product model
| Concern | Fields |
|---|---|
| Identity | vendor_id, category_id, brand_id, name, slug |
| Pricing | price, old_price |
| Merchandising | badge (new / sale / bestseller), rating, reviews_count, is_featured, featured_until |
| Inventory | stock, released on fulfilment by OrderInventoryService |
| Digital goods | is_digital, digital_delivery_window, plus assets, licence keys, and entitlements |
| Content | description, long_description, return_days, delivery_method |
| SEO and i18n | seo_title, seo_meta, translations, seo_translations |
Variations
Variable products carry product_variations rows. Buyers pick
attributes on the storefront and the variation modal resolves the matching row:
it opens from any product card, lists one group per attribute, disables
unavailable combinations, and enables Add to bag only when the chosen
combination is in stock.
Shared taxonomy
Categories, brands, and attributes are shared platform-wide but can also be
vendor-owned. Each carries vendor_id and an is_global
flag: vendors create their own, and an administrator can promote an entry to
global with the "make global" action. Categories are a tree via
parent_id and can require approval before they appear.
Attributes carry values in attribute_values. The seeded demo
set includes Size, Color, Storage, and Capacity.
Bundles
ProductBundleService validates and replaces a bundle's ordered
item list. Variable children can be pinned to a specific variation rather than
left open, and cancellation restocks every bundle line through
OrderInventoryService.
A digital product owns digital_assets, optional
digital_license_keys, and per-purchase
digital_entitlements. DigitalDeliveryService owns the
lifecycle: an entitlement starts pending on order,
becomes active on delivery, and is revoked on
cancellation or refund. Download limits and expiry windows are enforced
server-side.
07 / Storefront
Cart, coupons, tax, shipping, and order placement
Cart
Carts live in carts and cart_items and are shared
between the website and the mobile apps. ShippingMeter produces
the product-aware free-shipping progress data the cart drawer renders, and
OrderInventoryService is the only place stock is decremented or
restored.
Discounts
| Table | Role |
|---|---|
coupons | Discount rules: type, value, minimum spend, maximum discount, usage limit and count, expiry, free-shipping flag, and scope. |
coupon_customer | Per-customer redemption limits. |
customer_vouchers | Vouchers issued to customers, including loyalty tier redemptions. |
loyalty_redemption_tiers | Tier definitions: name, points cost, voucher value, minimum spend. |
VoucherService decides which coupons are publishable as
vouchers and serialises the voucher cards the apps display.
Tax and shipping
TaxService is a country-and-state aware tax engine with tax
classes, rates, and priorities, so it behaves the way a WooCommerce store does.
Shipping is configured per method with a flat rate, a free-shipping threshold,
and a free-shipping zone. Both are managed from
/admin/settings/shipping and /admin/settings/taxes.
Order placement
OrderPlacementService is the single canonical path for placing
an order, used by both the website and the API. It revalidates, server-side,
everything the client submitted: order ownership, gateway availability, stock,
coupon validity, shipping cost, and tax. Totals are always recomputed on the
server; the client total is never trusted.
That is the entire point of this service. If you add a checkout entry point,
route it through OrderPlacementService rather than writing
totals by hand.
Orders carry a human-readable code such as
MN-98412, a denormalised customer name and email for display, and
snapshot shipping details so historical invoices stay accurate after a customer
edits their address book.
08 / Storefront
Payment gateways
Fourteen payment methods ship in the box: twelve online gateways plus cash
on delivery and bank transfer. They are defined as constants on
PaymentGatewayService:
| Gateway | Key | Configuration fields |
|---|---|---|
| Stripe | card | mode, publishable key, secret key |
| SSLCommerz | sslcommerz | mode, store id, store password, currency |
| PayPal | paypal | mode, client id, client secret, currency |
| Mollie | mollie | mode, API key, currency |
| Paystack | paystack | mode, secret key, currency |
| Flutterwave | flutterwave | mode, secret key, webhook hash, currency |
| Razorpay | razorpay | mode, key id, key secret, currency |
| Mercado Pago | mercadopago | mode, access token |
| MyFatoorah | myfatoorah | mode, API token |
| Xendit | xendit | mode, secret key |
| PhonePe | phonepe | mode, merchant id, salt key, salt index |
| Midtrans | midtrans | mode, server key |
| Bank transfer | bank_transfer | label, description, instructions, and a list of accounts (bank name, account name, account number) |
| Cash on delivery | cod | label, description, instructions, extra fee |
card means Stripe.
The internal key is card for historical reasons.
normalize() maps stripe to card, and
configKey() maps card back to stripe for
settings storage. Use card in code and stripe in the
settings table.
Configuration and availability
Credentials are stored in the settings table under
gw_<gateway>_… keys and read through
Setting::gateway(). They are deliberately not in
.env, so an operator can rotate a key from the admin panel
without touching the server. A gateway is only offered at checkout when it is
enabled and every required credential is filled, as decided by
PaymentGatewayService::isConfigured().
Admins configure all of this at
/admin/settings/payment-gateways.
Where the code lives
Order payments
CheckoutController holds a
pay* and an initiate* pair per gateway, plus
paymentSuccess, paymentCancel,
paymentFail, and the
paymentIpn webhook receiver.
Everything else
OnlineGatewayPaymentService
handles non-order payables: vendor package subscriptions, featured product
promotions, and wallet top-ups. It exposes one
launch() and one verify() for all twelve
gateways.
Gateway calls are hand-rolled cURL requests in
OnlineGatewayPaymentService. There is no vendor SDK in
composer.json, which keeps the install light but means signature
verification is your responsibility when you add a gateway.
Scope limits worth knowing
- Vendor-facing payments are restricted to
VENDOR_ONLINE_METHODS: Stripe, SSLCommerz, and PayPal only. - Wallet top-ups accept all twelve online gateways, but never
walletorbank_transfer. - Only three gateways support refunds: Stripe, PayPal, and SSLCommerz.
refundStripe,refundPaypal, andrefundSslCommerzlive in the admin controller.
09 / Storefront
Customer accounts, loyalty, wallet, and digital downloads
Accounts
Customers register with email and password or sign in with Google. The Google client id is served from the API so the apps and the site share one configuration. Password recovery is an OTP flow: request, verify, then reset. Addresses live in a per-customer address book.
Loyalty
LoyaltyService earns points on delivered and paid orders, lets
customers redeem tiers as vouchers, and expires stale balances. Every movement
is a row in loyalty_transactions with its type, points, linked
order or coupon, description, and expiry, so a balance is always auditable.
Administrators configure earning rules at /admin/loyalty and
can adjust any customer's balance by hand with a recorded reason.
Wallet
| Concern | Implementation |
|---|---|
| Balance | wallets, one per customer. |
| Ledger | wallet_transactions with type, amount, status, reference, method, description, the acting admin, and free-form metadata. |
| Top-ups | Pending by default; an administrator approves or rejects before the balance moves. |
| Manual adjustment | Administrators credit or debit any wallet from /admin/wallets. |
The use it or lose it rule is deliberate: a pending top-up that is never approved is reversed rather than left dangling.
Digital library
Purchased digital goods appear in the customer's library as
digital_entitlements with a download count, a download limit, and
an expiry. File delivery uses a signed URL with a thirty-minute expiry, and
each download is written to digital_download_logs.
A signed URL proves the link was minted by this server; it does not prove who is asking. Entitlement access is checked when the link is minted, and the file route must verify the caller before streaming. See the launch checklist before going live.
10 / Storefront
Pages, blog, menus, media, and the page builder
Pages
Built-in pages (home, about, contact, FAQ, privacy, terms) are edited at
/admin/pages/<slug>/edit. Additional pages are created with
the visual custom page builder at /admin/pages/custom, so
non-technical staff can compose landing pages without touching Blade.
The homepage hero is a separate editor at /admin/hero.
HomePageContent and HomePageSchema make it
theme-aware: each theme declares which content keys exist and what fields they
accept, so switching themes does not strand a half-configured homepage.
Menus
MenuService reads and writes the header and footer navigation
trees at /admin/pages/menus. Menus are nestable, and the reset
action restores the theme's shipped default rather than leaving an empty
menu.
Blog
Posts are managed at /admin/blog with draft and publish state,
cover imagery, categories, and bulk actions. Post content goes through the same
translation pipeline as products and pages.
Media library
MediaService backs a shared library at
/admin/media, with upload, delete, and bulk delete. Uploads are
written under storage/app/public and served through
PublicMedia::url().
Upload roots are enumerated by BackfillMedia and cover
products, categories, brands, blog, branding, customers, vendors, admins, and
AI output. The library is isolated per owner where it matters: a vendor sees
their own media, an administrator sees everything.
Do not trust the client extension or MIME type. Store uploads outside the document root or with a fixed safe extension, and never let an upload land on a path the web server will execute.
Cookie consent
The consent banner is configured at /admin/settings/cookie and
rendered by the shared layout partial, so it appears on every theme without
per-theme work.
11 / Storefront
Languages, RTL, and the translation editor
MONO ships English, Arabic, and Bengali. Arabic is a full right-to-left
experience, not a mirrored stylesheet: layouts, the admin and vendor panels,
and the storefront all flip, and PanelRtlLocalizationTest guards
that behaviour.
Two independent defaults
This is the detail that trips people up. Each language has a
web default and a separate mobile default, stored as
default and default_mobile. Setting English as the
web default does not make English the app default. Admins toggle each
independently at /admin/languages.
| Capability | Where |
|---|---|
| Enable or disable a language | /admin/languages |
| Set the web default | /admin/languages |
| Set the mobile default | /admin/languages, the mobile toggle |
| Edit translations | /admin/languages/<language>/translations |
| Switch locale, signed in | /locale/<code>, /admin/locale/<code>, /vendor/locale/<code> |
Where strings live
Interface strings
PHP translation files under
lang/en, lang/ar, and lang/bn, with
JSON files for the long-tail strings.
Mobile strings
Dedicated lang/mobile-en and
lang/mobile-ar namespaces, served to the apps from the
/translations/{code} API endpoint.
Content translation
Products, categories, pages, and posts all carry a translations
column, with a separate seo_translations on products so metadata
can be translated independently of body copy. Bulk translation runs through
AiTranslationService, which chunks long fields and keeps every
field that succeeded, returning the list of anything it had to skip rather
than failing the whole record.
12 / Vendor panel
Vendor dashboard and store operations
The vendor portal is served from /dashboard behind the
auth:vendor guard, vendor.verified, and
vendor.approved middleware. Each vendor operates inside an isolated
tenancy boundary: every query is scoped to vendor_id, so merchants
only ever see and manage their own store catalog, orders, media, and finances.
Dashboard metrics
The landing page provides immediate operational visibility for merchants:
- Net Earnings (30d): Net income from paid, fulfilled items minus marketplace commission and refunds.
- Order Volume: Total order count with monthly delta comparisons.
- Average Order Value (AOV): Average transaction spend across store orders.
- Live Products: Published catalog count with inventory growth metrics.
- AI Usage & Quotas: Real-time consumption meter for text generation tokens, translation tokens, and generated product imagery. Displays clear status indicators when allowances are reached.
- 12-Week Revenue Trajectory: Rolling financial bar chart plotting sales momentum.
- Top Products Leaderboard: Top 5 revenue-generating items in the vendor's catalog.
Vendor portal navigation
| Module | Route | Capabilities |
|---|---|---|
| Overview | /dashboard | High-level store performance, revenue charts, and quota indicators. |
| Manage Products | /dashboard/products | Full catalog CRUD, attribute variations, digital asset uploads, and bundle manager. |
| Media Library | /dashboard/media | Dedicated vendor image and document repository scoped to the merchant. |
| Manage Orders | /dashboard/orders | Order status updates, customer delivery info, and 1-click digital delivery dispatch. |
| Delivery & Pickup | /dashboard/delivery | Operational delivery tracking and shipping partner handoffs. |
| Direct Messaging | /dashboard/messages | Real-time peer-to-peer customer chat powered by Ably channels. |
| Settlements & Ledger | /dashboard/settlements | Itemized commissions, sales ledger, and CSV financial exports. |
| Withdrawals | /dashboard/withdrawals | Payout requests to bank accounts or payment channels against system minimum thresholds. |
| Store Followers | /dashboard/followers | Customer audience engagement and broadcast conversation initiator. |
| Packages | /dashboard/packages | Tiered vendor membership upgrades with listing quotas and commission discounts. |
| AI Credits | /dashboard/ai-credits | Credit pack purchases for AI description generation and automated translations. |
| Store Settings | /dashboard/settings | Store name, banner, logo, business address, and payment preferences. |
13 / Vendor panel
Products, digital fulfilment, settlements, and AI
Vendors manage their complete ecommerce lifecycle from the web dashboard, supporting physical goods with complex variations as well as instantly deliverable digital goods.
Catalog and variations
Product creation in DashboardController handles both simple and
variable products. Variable products synchronize attribute combinations (sizes,
colors, fabrics) using VariationSyncService:
- Each variation has its own SKU, inventory stock, regular price, and sale price.
- Dynamic matrix creation generates all valid permutations without manual database configuration.
- Vendors can request new categories (
/dashboard/categories/request) or register custom brand names (/dashboard/brands) subject to admin review.
Product bundles
The bundle editor at /dashboard/product-bundles lets vendors group
complementary products into discounted bundle packages. ProductBundleController
validates inventory across all bundle items and ensures accurate pricing on checkout.
Digital goods & instant delivery
Products marked is_digital support downloadable files, license keys,
and digital entitlements. When an order is placed and paid:
- The vendor accesses
/dashboard/orders/{order}and triggersorders.digital-deliver. - The system mints a
DigitalEntitlementrecord with access limits and secure download tokens. - The customer immediately receives download access in their account library and via email notification.
Settlements and withdrawals
Every completed sale generates a settlement row calculating the exact revenue breakdown:
Net Vendor Payout = Item Subtotal - Platform Commission + Tax (if vendor-collected)
The settlements table at /dashboard/settlements tracks pending,
available, and paid earnings. Vendors request payouts via /dashboard/withdrawals
once their balance satisfies the minimum withdrawal threshold configured in platform settings.
Featured sponsorships
Vendors can promote individual products to prime homepage and category slots
via /dashboard/products/{product}/feature. The vendor chooses a featured
duration package and completes payment directly in the panel using Stripe,
SSLCommerz, or PayPal. Upon payment verification, the product is flagged
is_featured = 1 until the expiration timestamp.
Vendor AI suite
The dashboard incorporates integrated AI tooling powered by OpenAI / Google Gemini providers:
- Product Description Writer: Generates compelling ecommerce copy and bullet points from minimal attribute inputs.
- Multilingual Translation: 1-click translation of titles and descriptions across English, Arabic, and Bengali.
- Credit Packs: When monthly package quotas are exhausted, merchants purchase non-expiring AI credit packs directly through
/dashboard/ai-credits.
14 / Admin panel
Dashboard and admin navigation
The admin panel is served from /admin behind
auth:admin. The sidebar is defined as PHP arrays in
resources/views/layouts/admin.blade.php and filtered by
permission, so an administrator only ever sees the modules they can actually
use. The same permission keys guard the routes, which means the menu is not
the security boundary — the middleware is.
Navigation groups
| Group | Modules |
|---|---|
| Overview | Dashboard |
| Commerce | Orders, Invoices, Coupons, Loyalty, Reviews |
| Notification | Push notifications, Newsletter, Promos |
| Customers | Customers |
| Marketplace | Vendors, Delivery pickup, Applications |
| Content | Pages, Blog, Media |
| Communication | Messages, Inbox |
| Administration | Administrators, Languages |
| Themes & appearance | Themes |
| Settings | Settings |
Dashboard
The landing page shows revenue, order, vendor, and customer totals each with a thirty-day delta, a revenue chart, order status counts, the six most recent orders, the five most recent pending vendor applications, and the five top vendors. Every figure is computed from current rows rather than a cached summary, so it cannot drift.
Global admin search
/admin/search is a JSON endpoint that searches a fixed index of
roughly forty admin pages plus records, so an operator can jump straight to a
setting or an order from anywhere in the panel.
Your own profile
/admin/profile edits your name, email, avatar, and password.
It is deliberately outside every permission gate: changing your own password
must never depend on a role assignment that someone else controls.
15 / Admin panel
Administrators, roles, and the permission system
There is no admin self-registration. The first account is created by the
installer; every account after that is created by another administrator at
/admin/administrators. Administrators authenticate against the
admin guard and the App\Models\Admin model, which is
entirely separate from customers and vendors.
How permissions work
Roles and permissions are hand-rolled, not Spatie. Five tables carry it:
roles, permissions, the role_permission
pivot, the admin_role pivot, and admins. A role
carries an is_super flag, and
Admin::hasPermission() returns true unconditionally for a super
administrator.
Routes declare a permission through the permission: middleware.
Pipe-separated values mean "any of", so
permission:products.manage|blogs.manage|pages.manage grants access
to any one of the three.
Seeded roles
| Role | Super | Access |
|---|---|---|
super_admin | Yes | Everything, plus the bypass in hasPermission(). |
administrator | No | All 32 permissions. Effectively identical day to day; it simply cannot grant itself new ones by virtue of existing. |
editor | No | dashboard.view, orders.view, coupons.manage, reviews.manage, products.manage, customers.manage. |
Administrators and roles are managed at
/admin/administrators and /admin/roles, guarded by
admins.manage and roles.manage.
The thirty-two permissions
| Group | Permissions |
|---|---|
| Overview | dashboard.view |
| Commerce | orders.view, coupons.manage, loyalty.manage, notifications.manage, promos.manage, reviews.manage, products.manage, customers.manage, wallets.manage |
| Marketplace | vendors.manage, vendors.impersonate, registrations.manage, packages.manage, delivery.settings.manage, applications.manage, payouts.manage, delivery.manage |
| Administration | messages.manage, languages.manage, themes.manage, shipping.manage, taxes.manage, locations.manage, payments.manage, settings.manage, blogs.manage, pages.manage, media.manage, roles.manage, admins.manage |
vendors.impersonate is deliberately not bundled into
vendors.manage. "Login as vendor" signs you in as a seller with
their full session, so it should be held by as few people as possible and
revoked when someone leaves.
16 / Admin panel
Orders, invoices, and refunds
Everything in the commerce group sits behind orders.view. The
order list filters by status, including the returned state, and
the detail page is the hub for everything about a purchase.
Order actions
| Action | Route | Effect |
|---|---|---|
| Create | POST /admin/orders | Manual order, with product and customer search endpoints behind it. |
| Change status | PATCH /admin/orders/{order}/status | Drives customer notifications and loyalty accrual. |
| Change payment | PATCH /admin/orders/{order}/payment | Status, method, and reference. |
| Generate invoice | POST /admin/orders/{order}/invoice | Renders a PDF through dompdf. |
| Email invoice | POST /admin/orders/{order}/invoice/send | Sends the rendered document. |
| Delete | DELETE /admin/orders/{order} | Single, or bulk via bulk-destroy. |
Invoices
InvoiceService issues numbered invoices and is idempotent per
(kind, trigger, reference), so a repeated trigger cannot produce a duplicate
document. Each invoice stores its number, sequence, fiscal year, kind, trigger,
bill-to details, a line breakdown, the company block as a JSON snapshot, the
generated PDF path, and its email delivery status and error.
Triggers are configurable at /admin/settings/invoices: order
placed, paid, delivered, status change, refund, wallet adjustment, package
purchase, and featured promotion. The number prefix, tax ID, and footer are set
in the same place.
Regenerating supersedes rather than stacks, and the invoice list at
/admin/invoices shows the current document for each order plus
the full history of downloads and re-sends.
Refunds
Refunds are supported for three gateways only: Stripe, PayPal, and
SSLCommerz. The admin controller exposes refundStripe,
refundPaypal, and refundSslCommerz. There is no
generic refund path, because a generic path would be a generic way to lose
money.
17 / Admin panel
Catalog administration
The whole catalog block shares one permission,
products.manage. That is a deliberate simplification: a catalog
that a role can add to but not moderate is worse than one it cannot touch at
all.
Listing and editing
- Create and edit products with digital assets, variations, SEO, and per-field translations.
- Bulk and single delete.
- Digital assets, removed files, and bulk licence keys are handled by
DigitalProductService, shared with the vendor form.
Bundled sets
- Create and edit bundles at
/admin/product-bundles. - Candidate lookup for choosing line items.
- Variable children can be pinned to a specific variation.
- Cancellation restocks every line.
Paid promotion
- Feature or unfeature any product by hand.
- Approve or reject vendor featured-placement requests.
- Define and price the featured packages themselves.
featured:expireclears lapsed placements automatically.
Taxonomy approval
Vendors create their own categories, brands, and attributes. Those entries are pending until an administrator acts on them:
- Categories are a tree via
parent_idand are approved or rejected at/admin/categories. - Brands and attributes can be promoted to global with "make global", which makes them available to every vendor.
- Every list view has a JSON search endpoint, which is what the pickers in the product forms call.
Review moderation
Reviews are managed at /admin/reviews with approve, reject,
status change, and bulk delete. ProductReviewService resolves who
is allowed to review what from the review settings, so the eligibility rule is
one decision rather than one per entry point.
18 / Admin panel
Customers, wallets, coupons, and loyalty
customers.manageCustomer accounts
- List, create, edit, and delete customer accounts.
- Bulk delete for cleanup.
- Search powers the manual-order customer picker.
wallets.manageWallets
- Wallet list, single wallet detail, and the full transaction ledger.
- Pending top-ups queue for approval or rejection.
- Manual credit and debit with an attributed administrator.
coupons.manage · loyalty.manageDiscounts and points
- Coupon CRUD, bulk delete, and scoped target search.
- Loyalty settings and redemption tier CRUD.
- Manual point adjustment with a customer search.
Wallet approval flow
- Customer requests a top-up.
The request is written to
wallet_transactionsas pending. Money has not moved and the balance has not changed. - It appears in the admin queue.
/admin/wallets/pendinglists every outstanding request with its method, reference, and amount. - An administrator approves or rejects.
Approval credits the wallet and records the administrator. Rejection leaves the balance untouched. Neither is silent: both are ledger rows.
Unapproved requests that expire are reversed rather than left open, which is why a pending top-up never becomes a surprise balance later.
Every transaction row carries the acting admin_id when a person
caused it, plus a reference, method, and free-form metadata. If you add a
balance path, populate all four.
19 / Admin panel
Vendors, packages, settlements, and withdrawals
This is the largest group in the admin panel, and it is split across three permissions so that approving a seller, selling them a plan, and paying them are three separate decisions.
Getting vendors on the platform
| Surface | Route | Permission | What it does |
|---|---|---|---|
| Applications | /admin/applications | applications.manage | Submissions from the public "become a vendor" form, with per-item and bulk status actions. |
| Registrations | /admin/vendors/registrations | registrations.manage | Approval workflow with a configurable approval mode and its own settings page. |
| Vendors | /admin/vendors | vendors.manage | CRUD, verify toggle, suspend toggle, bulk delete. |
| Impersonation | POST /admin/vendors/{vendor}/impersonate | vendors.impersonate | Opens a full vendor session as that seller. |
| Store FAQs | /admin/faqs | vendors.manage | Per-store FAQs, with translations. |
Verification, suspension, and approval are independent states. A verified vendor can still be suspended, and suspending one does not delete their catalog.
Subscription packages
Vendors buy plans. A vendor_packages row carries price,
duration, a benefits array, and either a global commission or a per-package
commission percentage and fixed amount. VendorBillingService owns
the maths, and subscriptions snapshot price, duration, and commission at the
moment of purchase so a later plan edit cannot rewrite history.
Lifecycle
- Requested, then approved, rejected, or cancelled by an administrator.
- Payment status tracked separately from subscription status.
vendor-packages:notify-expirywarns vendors before a plan lapses.
Settlement lines
- One line per vendor and subscription.
- Gross, discount, commission, vendor earning, admin earning.
- Refund columns so a reversed commission stays visible instead of vanishing.
Money out
- Settlements at
/admin/vendors/settlementsshow the full ledger: gross, discount, commission, vendor earning, and admin earning per line. - Withdrawal requests at
/admin/vendors/withdrawalsare approved or rejected; paid and rejected history is kept separately at/admin/vendors/withdrawals/history. - Global presets at
/admin/vendors/withdrawal-presetsdefine the supported methods (bank transfer, PayPal, bKash, and so on). - Per-vendor accounts at
/admin/vendors/{vendor}/withdrawal-methodshold the payout destination for that seller.
The payouts group must stay above the vendors group in
routes/web.php. The static withdrawal-presets path has
to match before the /vendors/{vendor} wildcard swallows it. If you
reorder those groups, that page will 404.
AI credit packs
Sellors can buy token packs for the AI suite. Packs are defined at
/admin/vendors/ai-packs, purchases are approved or rejected, and a
pack can also be granted directly to a vendor. AiCreditPurchaseObserver
fires the "credits added" notification when a purchase becomes active from
any flow, and de-duplicates it against the explicit notification call
so sellers are not told twice.
20 / Admin panel
Delivery operations
Delivery covers the physical path from a vendor's shelf to a customer's door. It is split into operations and configuration, because the people who run shipments are rarely the people who set courier contracts.
Operations, behind delivery.manage
| Surface | Route | What it does |
|---|---|---|
| Pickup requests | /admin/delivery/pickup-requests | Vendor to warehouse pickups. |
| Drop-off requests | /admin/delivery/dropoff-requests | Warehouse to customer drop-offs. |
| Request lifecycle | POST /admin/delivery/requests/{id}/… | Approve, reject, schedule, assign personnel, change status, add a note, set the warehouse. |
| Shipments | /admin/delivery/shipments | Outbound shipments with courier, tracking number, and tracking URL. |
| Tracking | /admin/delivery/tracking | One merged timeline per order. |
Every state change on a request is written to
delivery_request_events, so the timeline is a record rather than a
reconstruction. TrackingService merges request events and shipment
events into the single per-order timeline that customers and vendors both see.
ShipmentService moves the parent order forward only when the
evidence is unambiguous. It will not downgrade an order to match a lagging
shipment, because a late courier scan should never tell a customer their
delivered order is processing again.
Configuration, behind delivery.settings.manage
CRUD for delivery staff at /admin/delivery/personnel, assignable to any request.
/admin/delivery/locations, created, updated, and deleted.
/admin/delivery/couriers, the courier partners available when creating a shipment.
/admin/delivery/settings via DeliverySettings, including the delivery_notify_* switches that gate vendor, admin, and customer notifications.
DeliveryService is the single source of validation shared by the
vendor panel, the admin panel, and the API, so a request created from any of
the three is checked the same way.
21 / Admin panel
Chat, messages, push, and newsletters
Realtime chat inbox
The admin inbox is the platform operator's view of every customer-vendor conversation. It uses Ably for delivery with a REST polling fallback, so the inbox still works if the realtime connection drops.
| Route | Rate limit | Purpose |
|---|---|---|
inbox | none | Conversation list |
inbox/{conversation} | none | Message thread |
inbox/reply | 20 / 1 min | Send as the platform |
inbox/{id}/poll | none | Polling fallback |
inbox/{id}/delivered | 60 / 1 min | Read and delivered receipts |
inbox/ably/token | 30 / 1 min | Short-lived client token |
inbox/messages/remove | none | Moderation removal |
The Ably secret never leaves the server. AblyTokenService mints
short-lived client tokens per request, and routes/channels.php
authorises each private channel against the correct guard. Admin access to a
conversation requires both messages.manage and that the
conversation belongs to the platform vendor.
It arrives from customers and vendors. Escape it on render, and do not allow it to introduce markup. Chat attachments are served through signed URLs with a one-hour expiry, and the download route must verify the caller.
Contact messages
The public contact form feeds /admin/messages.
ContactMessageService persists the message first and then treats
the admin notification email as best-effort, so a mail failure never loses a
customer's enquiry.
Push notifications
/admin/notifications/push broadcasts to customer devices over
Firebase Cloud Messaging. FcmClient implements the FCM HTTP v1 API
directly, including JWT-bearer OAuth2 token caching, so no Firebase Admin SDK
is required. SendPushNotification fans out across a customer's
registered devices and prunes tokens that have gone dead.
Service-account credentials are uploaded at
/admin/settings/firebase and stored in the database, not
.env.
Newsletters
/admin/newsletter manages subscribers, a campaign editor, CSV
export, test sends, and dispatch.
SendNewsletterCampaign chunks through active subscribers in one
worker run with a thirty-minute timeout, which is why a queue worker matters on
a real site.
Promo banners
/admin/promos controls the promotional banner shown on the
mobile apps' home screen. It is a separate surface from the storefront hero, so
a campaign can target app users without changing the website.
22 / Admin panel
AI tools, providers, and vendor quotas
The AI toolset is available to authenticated administrators for translating
content, generating copy, and generating images. Providers and model selection
live at /admin/settings/ai.
What administrators can do
POST /admin/translatetranslates arbitrary content, throttled to 20 requests per minute.POST /admin/translate/taxonomybulk-translates taxonomy in one pass, throttled to 10 per minute.POST /admin/generateandPOST /admin/generate-imageproduce copy and imagery, gated byproducts.manage|blogs.manage|pages.manage.
Translation runs through AiTranslationService, which chunks
long fields and keeps every field that succeeded. A record with one
untranslatable field is partially translated rather than rolled back, and the
response lists what was skipped.
Vendors get metered access
Sellers reach the same tools from the vendor panel and the vendor API, but
through VendorAiQuotaService, which enforces three separate
pools:
Text generation
Tokens consumed by copy and description generation.
Translation
A separate token pool, because translation volume scales very differently from authoring.
Images
Counted per generated image rather than per token.
Vendor AI endpoints are throttled to 30 requests per minute each, and
quota is checked before the call, not after. Credits are drawn down from the
active package allowance, and unused allowances expire rather than rolling
over: ai-credits:cancel-expired runs every minute.
Every AI call is recorded in ai_usage with its provider, model,
token counts, and cost attribution, so you can reconcile spend against
packages.
Provider credentials are stored through the settings store and edited from
the admin panel. They are never written to .env, and they are
never returned to the browser.
23 / Admin panel
Themes, app themes, and colours
Everything here sits behind themes.manage and lives under
/admin/appearance.
| Surface | Route | What it does |
|---|---|---|
| Branding | /admin/appearance/branding | Site logo, favicon, and brand marks. |
| Storefront themes | /admin/appearance/themes | Install, activate, and remove storefront themes. |
| App themes | /admin/appearance/app-themes | Activate the mobile app's visual system. |
| Colours | /admin/appearance/colors | Edit and reset the active palette. |
Palette resolution
ThemePalette maps a manifest palette onto CSS custom
properties. Each colour carries a label, a default, and a variable name, so an
administrator sees "Button background" rather than
--el-primary-500. The palette also carries shape radii, an
isDark flag, and status colours for the seven order statuses, so a
dark theme automatically gets readable status badges instead of inheriting light
ones.
Storefront and app themes move independently
Switching the storefront theme does not change the mobile app's layout, and
switching the app theme does not change the website. They share a colour
palette, but each has its own activation state. Legacy
/settings/branding, /settings/themes, and
/settings/colors URLs redirect here, so old bookmarks and old
documentation links keep working.
Uninstall drops every table the manifest declares. The
mono theme is kept outside the removable tree for exactly this
reason.
24 / Admin panel
Settings reference
/admin/settings redirects to the shop settings page. The rest
of the group is a set of focused screens rather than one long form, so an
operator changes one thing at a time.
| Page | Route | Permission | Contains |
|---|---|---|---|
| Shop | /admin/settings/shop | settings.manage | Site name, tagline, contact details, currency, free-shipping threshold, flat shipping, product grid columns, pagination. |
| Shipping | /admin/settings/shipping | shipping.manage | Shipping rates, flat rate, free-shipping zone, and shipping method CRUD. |
| Locations | /admin/settings/countries | locations.manage | Countries with nested states and cities. |
| Taxes | /admin/settings/taxes | taxes.manage | Tax classes and rates with priorities. |
| Payments | /admin/settings/payment-gateways | payments.manage | All fourteen gateway configurations. |
| Invoices | /admin/settings/invoices | settings.manage | Enable, triggers, number prefix, tax ID, footer. |
| SMTP | /admin/settings/smtp | settings.manage | Mail server and a test-send button. |
| Firebase | /admin/settings/firebase | settings.manage | FCM web config and service-account upload. |
| Scheduled jobs | /admin/settings/scheduled-jobs | settings.manage | Job table with frequency, enable/disable, and run-now. |
| Chat | /admin/settings/chat | settings.manage | Ably configuration. |
| Mobile app | /admin/settings/mobile-app | settings.manage | App-facing configuration served to both Flutter clients. |
| AI | /admin/settings/ai | settings.manage | Providers, model list, and translator, generator, and image model selection. |
| Cookie consent | /admin/settings/cookie | settings.manage | Consent banner copy and behaviour. |
Everything above is key and value rows in the settings table,
not environment variables. That is deliberate: it lets an operator change a
mail server or rotate a payment key without shell access. It also means
backing up the database backs up your configuration.
25 / Admin panel
Administrators, roles, languages, and media
These four modules sit in the "Administration" group. They are the ones that change who can do what, in what language, and with what imagery.
Administrators
/admin/administrators creates, edits, and deletes admin
accounts behind admins.manage. There is no public registration
path: the installer creates the first account, and every account after that
comes from this screen.
Roles
/admin/roles is where permissions are assigned. Each role is a
name, a label, an is_super flag, and a set of permissions drawn
from the 32 available. Changing a role's permission set changes it for every
administrator holding that role, immediately.
An is_super role bypasses hasPermission()
entirely. If you remove super-admin status from every account, nobody can
restore it through the panel and you will be editing the database by hand.
Languages
/admin/languages manages the three shipped languages plus any
you add. Each has a web default and an independent mobile default, an
enable/disable switch, and a per-language translation editor at
/admin/languages/{language}/translations.
Media
/admin/media is the shared library behind every image picker in
the panel, with a JSON listing endpoint, upload, delete, and bulk delete.
Uploads land under storage/app/public in the owner-appropriate
subdirectory and are served through PublicMedia::url().
The library is owner-isolated: a vendor only ever sees their own media, while
an administrator sees everything. MediaIsolationTest guards that
boundary.
26 / Backend
API version 1: shape, auth, and limits
Both Flutter clients talk to the same versioned JSON API. The prefix is set
once in bootstrap/app.php as api/v1; there is no
content negotiation, so the version is explicit in every path.
https://your-domain.com/api/v1Response envelope
Success
{ "success": true, "data": …, "meta": { … } }.
Pagination and other collection metadata live in meta, never
inside data.
Error
{ "success": false, "error": { "message": …,
"code": …, "fields": { … } } }. fields is present only on
validation failures. Rendered centrally by
App\Support\ApiException.
Authentication
Sanctum personal access tokens, sent as Authorization: Bearer.
Two middleware variants exist, and the difference matters when you add a
public endpoint that still wants to know who is asking:
auth:sanctumrequires a valid token and rejects without one.auth.optionalresolves the user when a token is present and never rejects. The public catalog runs on it so that wishlisted and collected flags can be resolved for a signed-in customer without turning browsing into a login wall.
Rate limits
There is no global limiter. Limits are declared per route:
| Route | Limit |
|---|---|
POST /contact/send | 10 / min |
POST /products/{id}/reviews | 10 / min |
POST /checkout/sessions, wallet-pay, cod-pay | 30 / min |
| Vendor AI generate, generate-image, translate | 30 / min |
Vendor messages/start | 10 / min |
Vendor messages/{id}/reply | 30 / min |
Login, register, and the OTP endpoints have no rate limit attached. If you are hardening a public deployment, add a limiter to those before launch; see the launch checklist.
Signed routes
Two download routes sit deliberately outside auth:sanctum and
are guarded by signed URLs with expiry instead: chat attachments at one hour,
and digital product files at thirty minutes. Signing replaces the bearer token
in the query string so the URL can be handed to a browser or a download
manager without leaking a session credential.
27 / Backend
Customer API surface
Roughly forty endpoint families serve the customer app. The shape below is grouped by what a user is doing rather than by controller, because that is how the apps consume it.
Session
POST /auth/login,/auth/register,/auth/googleGET /auth/google-configreturns the client idPOST /auth/forgot-password/send-otp,/verify-otp,/resetPOST /auth/logout,/auth/change-passwordGETandPUT /auth/profile
Discovery
GET /home,/home/discover,/home/featuredGET /categories,/products,/products/{id},/products/{id}/relatedGET /products/{id}/reviews,/flash-saleGET /search/suggestions,/vendors/{id}GET /vouchers— the public voucher pool
Cart and checkout
GET /cart,POST /cart/add,POST /cart/qty,DELETE /cart/{productId},POST /cart/clearPOST /coupons/apply,DELETE /couponsGET /checkout,POST /checkout/placePOST /checkout/paypal/createfor native PayPalPOST /checkout/sessionsopens a hosted web checkout;/checkout-sessions/{uuid}/status,/cancel,/wallet-pay, and/cod-paycomplete itPOST /orders/{id}/payment-sessionretries payment for an existing order
Account
GET /orders,/orders/{id},/orders/{id}/tracking,/orders/{id}/reorderGET /invoices/{id}GET /downloads,/downloads/{entitlement}GET,POST,PUT,DELETE /addressesPOST /following,POST /vendors/{id}/follow
Money
GET /wallet,/wallet/transactions,/wallet/topup-methods,POST /wallet/topupGET /loyalty,/loyalty/transactions,POST /loyalty/redeemGET /vouchers/saved,POST /vouchers/{code}/save,GET /vouchers/{code}- Wishlist:
GET,POST, andDELETE /wishlist
Engagement
GET /notifications,/notifications/unread-count,/notifications/read-all, and per-item read and deletePOST /notifications/register-tokenandunregister-tokenfor FCMGETandPUT /settings/notificationsGET /chat,/chat/conversations,/chat/{id}/poll,POST /chat/send,GET /chat/tokenPOST /contact/sendfor the contact form
Content and localisation
GET /content/about, /content/contact, and
/content/page/{slug} return CMS content.
GET /country, /country/states/{cc}, and
/country/cities/{state} drive the address forms.
GET /languages and GET /translations/{code} supply
the app's interface strings, including the separate mobile namespaces.
Controllers return API Resources, not raw models:
ProductCardResource, ProductDetailResource,
OrderResource, WalletResource, and so on. Add a field
to a Resource rather than to the model, or you will change the admin panel's
own payloads by accident.
28 / Backend
Vendor API surface
Everything a seller needs is namespaced under
/api/v1/vendor, served by sixteen controllers in
Api/V1/Vendor/. Vendor auth, profile, and settings mirror the
customer equivalents.
Store
GET /dashboard— the seller overviewGET,POST /products;GET,PUT,DELETE /products/{id}GET /categories,/brands,/attributes,/languagesas reference dataGET,POST /product-bundlesand the item routes, with/product-bundles/candidatesfor the pickerGET,PUT /settings,PUT /settings/payouts,/settings/notifications
Orders and delivery
GET /orders,/orders/{id}PATCH /orders/{id}/statusfor fulfilmentPOST /orders/{order}/digital-deliver/{entitlement}for per-item digital deliveryGET,POST /delivery/requestsandPOST /delivery/requests/{id}/cancelGET /delivery/history,/delivery/tracking,/delivery/tracking/orders/{id}POST /delivery/shipments,PATCH /delivery/shipments/{id}/status
Growth
POST /categories/requestandGET /categories/my-requestsGET,POST,PUT,DELETE /brandsand/attributes, including/customlistsGET /packages,POST /packages/{package}/checkoutGET /featured/packages,/featured/requests,POST /products/{id}/feature/checkout
Money and engagement
GET /wallet,/settlements,/withdrawals,POST /withdrawalsGET /ai/quota,/ai/creditsand the three generation endpointsGET,POST /faqsandGET /followersGET /media,POST /media/upload,DELETE /media/{id}- The full notification set under
/vendor/notifications
Every mutation on a vendor route is scoped to the authenticated vendor's own records, independent of the client payload. If you add a vendor endpoint, keep that scoping in the controller or a policy; do not trust a vendor id from the request body.
29 / Backend
Schema overview
The application carries 147 migrations producing 94 tables, backed by 78 Eloquent models. Most of the volume is marketplace and delivery surface rather than framework scaffolding.
| Domain | Tables |
|---|---|
| People and access | admins, roles, permissions, role_permission, admin_role, customers, vendors, vendor_applications, addresses, password_otps, device_tokens |
| Catalog | categories, brands, attributes, attribute_values, products, product_images, product_variations, product_bundle_items |
| Commerce | orders, order_items, carts, cart_items, wishlists, coupons, coupon_customer, customer_vouchers, reviews, invoices, shipping_methods, tax_classes, tax_rates |
| Money | wallets, wallet_transactions, vendor_payouts, vendor_packages, vendor_package_subscriptions, vendor_withdrawal_methods, vendor_withdrawal_presets, vendor_follows |
| Delivery | delivery_requests, delivery_request_events, shipments, shipment_tracking_events, delivery_personnel, delivery_locations, delivery_couriers |
| Digital goods | digital_assets, digital_license_keys, digital_entitlements, digital_download_logs |
| Engagement | conversations, conversation_messages, conversation_message_attachments, conversation_participants, customer_notifications, vendor_notifications, contact_messages, newsletter_subscriptions, newsletter_campaigns, ably_settings |
| Content and platform | pages, posts, media, languages, settings, themes, countries, states, cities, scheduled_jobs, app_checkout_sessions, loyalty_transactions, loyalty_redemption_tiers, featured_packages, featured_product_requests |
| AI | ai_providers, ai_usage, ai_credit_packs, ai_credit_purchases, ai_pack_consumptions |
Design decisions worth knowing
customer_name and email are copied onto the order, so a historical invoice still prints correctly after the customer renames their account or changes their address book.
Price, duration, and commission are copied onto the subscription row at purchase time, so editing a package never rewrites what a vendor was actually sold.
Runtime configuration lives in one settings table rather than in files, which is what makes the admin panel able to change mail servers and payment keys without server access.
Demo seeders
DatabaseSeeder orchestrates the rest:
RolePermissionSeeder creates the three roles and the 32
permissions, LocationSeeder fills countries, states, and cities,
TaxSeeder sets tax classes, and
PageSeeder creates the built-in pages. Beyond that,
RealisticOrderAndReviewSeeder generates plausible orders and
reviews, MonoDeskKitSeeder builds a full platform-vendor bundle
catalogue, and DemoDigitalProductsSeeder and
ScopedVoucherSeeder populate digital goods and vouchers per
audience.
The seeders create admin@mono.test and
jordan@example.com, both with the password
password, and eight vendors on the same password. They also embed
Firebase web keys. Run migrate without --seed on a
real deployment, or remove these accounts before the site is public.
30 / Backend
Services, jobs, events, and commands
Business rules live in services, not in controllers. If a rule is about
orders, money, delivery, or entitlement, it belongs in
app/Services.
The services that matter most
Order placement
OrderPlacementService is the canonical order path for web and
API. OrderInventoryService is the only place stock moves.
Billing
PaymentGatewayService and
OnlineGatewayPaymentService own gateway availability, launch,
and verification. VendorBillingService owns commission and
settlement arithmetic.
Fulfilment
DeliveryService validates requests from every entry point.
ShipmentService runs the shipment lifecycle,
TrackingService merges timelines, and
DeliveryNotificationService fans out the alerts.
Documents
InvoiceService numbers and renders invoices.
DigitalDeliveryService owns entitlement state.
LoyaltyService and VoucherService own points and
vouchers.
Messaging
ConversationService manages participants.
AblyTokenService mints short-lived client tokens so the Ably
secret never leaves the server.
Infrastructure
ScheduledJobService registers the database-driven job table.
MediaService, MenuService, and
HomePageContent support the storefront editing surfaces.
Jobs and events
Three queued jobs: SendNewsletterCampaign (chunked,
timeout = 1800), SendPushNotification, and
SendVendorPushNotification (both tries = 2, both
pruning dead device tokens). One observer,
AiCreditPurchaseObserver, fires the vendor notification when a
credit purchase becomes active from any flow and de-duplicates it.
Four broadcast events run through Ably: MessageSent,
MessageRemoved, ChatReceiptUpdated, and
VendorNotificationReceived.
Artisan commands
| Command | Purpose |
|---|---|
orders:cancel-expired | Cancels unpaid orders past their window and restores stock. |
ai-credits:cancel-expired | Reverses unsettled AI credit purchases. |
featured:expire | Clears lapsed featured placements. |
loyalty:expire-points | Expires stale point balances. |
badges:expire-new | Retires the "new" badge window. |
vendor-packages:notify-expiry | Warns vendors before a plan lapses. |
media:backfill | Re-resolves media records for the upload roots. |
catalog:export-release | Exports a catalogue artifact for the installer. |
release:install-catalog-themes | Installs catalogue theme packages on a release host. |
release:backfill-catalog-media | Restores catalogue imagery after an import. |
release:verify-schema | Verifies the expected tables exist before go-live. |
release:verify-schema after an update.
It checks that the expected tables are present, which catches a partially applied migration before it becomes a missing-column error in production.
31 / Backend
Going live: deployment and the pre-launch checklist
Production configuration
APP_ENV=productionAPP_DEBUG=falseAPP_URL=https://your-domain.com* * * * * cd /path && php artisan schedule:runphp artisan queue:work --tries=2php artisan release:verify-schemaPayment gateways reject non-HTTPS callbacks, Sanctum tokens must not travel in cleartext, and signed URLs are meaningless over an interceptable connection. Terminate TLS in front of the application and redirect HTTP permanently.
For an end-to-end visual walkthrough of setting up production cPanel hosting, database creation, queue crons, and SSL verification, watch the cPanel Video Guide or open it directly on YouTube ↗.
Pre-launch checklist
- Remove the demo data.
Delete the seeded admin, customer, and vendor accounts, or run
migratewithout--seed. Confirm no@mono.testaccount can still authenticate. - Delete the installer.
Remove
public/install.phpand confirmstorage/app/installed.lockexists. - Set the app key and rotate the seeded Firebase keys.
Run
key:generatefor a freshAPP_KEY, and replace the Firebase web configuration that the seeders wrote. - Configure only the gateways you need.
Fill in real credentials at
/admin/settings/payment-gatewaysand leave the rest disabled. A half-configured gateway is hidden from checkout automatically, which is the behaviour you want while you set it up. - Verify mail actually sends.
Use the test-send button at
/admin/settings/smtp. Until mail works, order confirmations, invoices, OTPs, and password resets all fail silently from the customer's point of view. - Confirm the cron and the queue worker are running.
Unpaid orders will not be cancelled, featured placements will not expire, and newsletters and push will not be delivered without them.
- Add rate limiting to the auth endpoints.
Login, register, and the OTP routes have no limiter in this build. Add one before exposing the site publicly.
- Review file upload handling.
Confirm uploads land outside any executable path, that extensions and MIME types are validated server-side, and that chat attachments and digital downloads verify the requesting user before streaming a file.
- Set a real admin password policy and rotate shared logins.
Particularly before granting
vendors.impersonateto anyone.
Backups
Back up the database and storage/ together. The database holds
every setting, every uploaded media record, and the entire wallet and settlement
ledger; storage/ holds the actual files and the generated invoice
PDFs. A backup of one without the other is not a backup.
An untested backup is a hypothesis. Restore into a scratch database and run
release:verify-schema to confirm the result is usable.