PLOCAL is a nationwide local business evaluation platform. It is built around one core model, repeated across every U.S. state, city, and category:
PLOCAL helps customers find local businesses they can trust, and helps business owners understand how professional, credible, visible, and customer-ready their business appears online. Unlike a simple listing directory, PLOCAL does not just confirm that a business exists — it evaluates it against a structured, transparent standard, then presents the outcome publicly.
People searching for a trustworthy local provider — contractors, salons, restaurants, and other local service businesses — across any U.S. state or city.
Businesses that want a professional evaluation of their online presence, and the opportunity to be publicly recognized for strong performance.
Nationwide search and browsing of evaluated businesses.
Structured Digital / Service / Trust assessment for every activated Business.
Community input that feeds qualification, not the final decision.
Business of the Day, Month, and Year, awarded on evaluated merit.
Invitation-only account for an activated Business to view its own status.
Staff workspace for reviewing submissions, evaluations, payments, and content.
Editorial content managed internally, published publicly.
A manually curated homepage sponsor carousel, unrelated to Evaluation or Recognition.
PLOCAL is a single Laravel application. It is primarily server-rendered: pages are built on the server using Blade templates and returned as complete HTML, rather than relying on a separate single-page-application frontend framework. This keeps the system simple to operate, deploy, and reason about.
| Layer | Responsibility |
|---|---|
| Frontend | Server-rendered Blade templates, page-specific CSS/JS, and standard HTML forms. No separate frontend build is required to serve the site. |
| Routes / Controllers | Accept an HTTP request, validate input, and hand off to the correct Application Service. Controllers stay thin. |
| Application Services | Coordinate one specific use case end-to-end (for example, activating a Business after payment, or casting a public vote). |
| Domain Rules | The business rules that must always hold true regardless of caller — for example, how the PLOCAL Score is calculated, or what counts as a valid U.S. state code. |
| Database | MySQL. The single source of truth for every Business, Submission, Payment, Evaluation, Vote, and Award. |
| Stripe | Handles the Evaluation Checkout payment and is the sole authority on whether a payment succeeded. |
| Sends invitation, notification, and transactional email through SMTP. | |
| Storage | Holds private submission evidence and public content media (Blog images, advertiser logos). |
The Laravel application lives entirely inside the backend/ directory of the
repository. The directories below are the ones an owner or developer will interact with most.
| Path | Purpose |
|---|---|
backend/app/Http/Controllers | Route entry points, grouped by Public, Business, and Internal. |
backend/app/Application | One class per real use case (submitting a business, casting a vote, issuing an invitation). |
backend/app/Domain | Pure business rules with no framework or database dependency (score calculation, state validation). |
backend/app/Infrastructure | Read-model implementations and integration-facing code. |
backend/app/Console/Commands | Artisan commands — production bootstrap, verification, and local developer tooling. |
backend/resources/views | All Blade templates: public/, business/, internal/, and shared layouts/ and partials/. |
backend/routes/web.php | Every public, Business, and Internal route. |
backend/database/migrations | The complete database schema history. |
backend/database/seeders | Reference-data seeders, including the one production-safe bootstrap seeder. |
backend/public | The web server document root — the only directory that should be exposed directly to the internet. |
backend/storage | Private submission evidence, logs, and framework cache — never web-exposed directly. |
backend/tests | The automated test suite covering the majority of product behavior. |
assets/ (repository root) | Shared CSS, JavaScript, images, and icon assets used by the public site. |
Every request follows the same predictable path through the backend:
Routes are grouped into three clearly separated areas:
/business, require a Business Portal login./internal, require staff login and role permission.Every state-changing request is validated before any Application Service runs. Internal routes are additionally protected by role-based permission checks (see Chapter 16 and Chapter 23), so a Reviewer account, for example, cannot perform an Administrator-only action even if it knows the correct URL.
Notification delivery (invitation emails, activation confirmations, support-case acknowledgements) is queued rather than sent inline, so a slow mail provider never blocks a visitor-facing request. See Chapter 26 for the current queue configuration.
| Integration | Purpose |
|---|---|
| Stripe | Evaluation Checkout payment and signed webhook confirmation. |
| SMTP (Google Workspace) | Transactional email delivery from info@plocal.com. |
| MySQL | Primary data store for every domain object in the system. |
PLOCAL's frontend is server-rendered. Laravel's Blade templating engine builds each page on the server and returns finished HTML — there is no separate JavaScript framework driving page navigation, and no client-side routing to keep in sync with the backend.
| Area | Location |
|---|---|
| Shared layout & navigation | resources/views/layouts, resources/views/public/partials |
| Public pages | resources/views/public |
| Business Portal pages | resources/views/business |
| Internal Portal pages | resources/views/internal |
The public site's header and footer are each a single shared partial included on every public page, so a navigation change made once is reflected everywhere automatically.
CSS and JavaScript are organized by page and by shared concern under the repository's
assets/ directory (bootstrap and vendor libraries, a shared custom stylesheet, and
page-specific stylesheets/scripts). Pages are responsive down to common mobile widths.
Public forms (Submit Your Business, Contact, Support) post directly to a Laravel route.
Where a form submits without a full page reload, it uses a plain JavaScript fetch
request against the same route and displays the JSON result inline — there is no separate API
layer or token-based authentication for these public forms.
The table below covers the primary pages a visitor encounters. Internal action-only endpoints are intentionally omitted — see Chapter 47 for a short reference of the most useful routes.
| Route | Purpose |
|---|---|
| / | Home — Business of the Day, featured Recognition, and entry points into the Directory. |
| /about.html | About PLOCAL — what the platform is and why it exists. |
| /contact.html | General contact form. |
| /faq.html | Frequently asked questions. |
| /support.html | Support request form (account, Profile, Evaluation, billing, accessibility, Trust & Safety). |
| /trust-and-safety.html | Trust & Safety overview. |
| /protect-yourself-from-fraud.html | Fraud-awareness guide for customers. |
| /terms-and-conditions.html | Terms & Conditions. |
| /accessibility-statement.html | Accessibility statement. |
| /how-plocal-evaluation-works.html | Public explanation of the Evaluation model. |
| /submit-your-business | Business Submission form (see Chapter 9). |
| /find-businesses | Nationwide directory search (see Chapter 7). |
| /categories.html | Curated category landing page. |
| /usa | National directory hub, branching into state and city hubs. |
| /{state}, /{state}/{city}, /{state}/{city}/{service} | Geography-based discovery hierarchy. |
| /recognition | Business of the Day, Month, and Year (see Chapter 14). |
| /blog, /blog/{slug} | Published editorial articles. |
| /pro/{businessSlug} | An individual Business Profile (see Chapter 8). |
/find-businesses is the current, primary nationwide search surface. The
geography hierarchy under /usa is a complementary, SEO-oriented browsing path
into the same underlying evaluated Businesses — both ultimately lead to the same Business
Profile pages.
PLOCAL supports all 50 U.S. states using canonical two-letter USPS state codes (for example,
CA, TX, NY). A Submission is only accepted with a valid
code from this canonical list — full state names or invalid codes are rejected at submission
time. Cities are created automatically the first time a legitimately paid, activated Business
reports that city — there is no need to pre-populate every city in the country in advance.

Find Businesses — nationwide search by category, state, city, and ZIP. Captured from the local development environment.
Results are shown in a neutral order. Placement is not determined by payment, votes, Awards, or Evaluation score — every eligible Business receives equal presentation weight in the result list.
Every evaluated Business has a public profile at /pro/{businessSlug}.

A live Business Profile, showing category, location, website screenshot, and Digital / Service / Trust result. Captured from the local development environment.
Profile pages remember how the visitor arrived, so browsing forward and backward stays inside the same context instead of jumping to an unrelated Business:
| Arrived From | Previous / Next Behavior |
|---|---|
| Newest Nominees | Chronological Nominee-to-Nominee navigation. |
| Recognition | Chronological navigation within the same Day, Month, or Year level. |
| Find Businesses | Navigation within the exact search-result context (same filters and page). |
| Direct Profile visit (no context) | Falls back to Find Businesses rather than fabricating an arbitrary neighbor. |

Submit Your Business — the public Submission form. Captured from the local development environment.
| Requirement | Value |
|---|---|
| Format | JPEG |
| Exact dimensions | 1068 × 646 pixels |
| Maximum file size | 150 KB |
Verified directly against the current Submission validation rules.
A successful Submission creates a durable record and immediately proceeds to Evaluation Checkout (Chapter 10). The Submission itself does not create a public Business Profile — publication only happens after payment is confirmed and the Business is activated (Chapter 11).
PLOCAL charges a fixed Evaluation service fee of $250 USD plus applicable taxes, collected through Stripe's hosted Checkout page.
| Environment | Description |
|---|---|
| Development / Sandbox | Uses Stripe test-mode keys. No real card is charged. |
| Production / LIVE | Uses Stripe live-mode keys. Real cards are charged real money. |
# .env — never commit real values
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
# Terminal 1 php artisan serve # Terminal 2 — forwards Stripe test events to your local app stripe listen --forward-to http://localhost:8000/webhooks/stripe
stripe listen command is a local development convenience. Production never
uses the Stripe CLI listener — it receives webhook events directly from Stripe over HTTPS at
the production domain (see Chapter 32).
Once Stripe's signed webhook confirms payment, PLOCAL provisions everything the Business needs in a single, idempotent sequence:
Idempotency: replaying the same confirmed payment event does not create a duplicate Business or a duplicate Evaluation Window — activation is safe to retry.
If activation did not fully complete (for example, a transient failure between steps), an Administrator can trigger a safe retry from Internal Payment Operations rather than needing any direct database intervention (Chapter 18).
Every activated Business is assessed across three dimensions, each scored from 0 to 10:
Each Evaluation runs on a fixed window of 96 elapsed hours from the moment the Business is activated. This is wall-clock elapsed time, not 96 business hours.
| Role | Responsibility |
|---|---|
| Evaluator | Performs the initial Digital / Service / Trust assessment for an assigned Nominee. |
| Reviewer | Reviews and can return an Evaluator's work for changes, or complete it. |
| Administrator | Holds full access, including the authority to make the Final Decision on Recognition. |
While a Nominee's Evaluation Window is open, the public can cast a Vote across the same three dimensions: Digital, Service, and Trust.

Recognition — Business of the Day, Month, and Year. Captured from the local development environment.
A qualifying Nominee that reaches the Evaluation and voting thresholds becomes a Candidate. An Administrator's Final Decision determines the outcome — confirmed as a Nominee, granted Honors, awarded Business of the Day, or declined. Honors and Day/Month/Year Awards are drawn from qualifying Candidates on a chronological basis.
Past Recognition remains publicly viewable. If an Award is later revoked or replaced, that history is preserved for transparency rather than silently deleted — a revoked or inactive Award is not shown as currently valid, but the record of it having occurred is retained internally.

Business Portal sign-in at /business/login. Captured from the local development environment.
The dashboard is a read-only status view scoped strictly to the account's own Business:
A Business account cannot view or influence another Business's data, and cannot access the Internal Portal under any circumstance — the two systems use entirely separate authentication guards.

Internal Portal staff sign-in at /internal/login. Captured from the local development environment.
| Role | Access Level |
|---|---|
| Administrator | Full access, including Final Decisions, Payment Operations, Users, and content management. |
| Reviewer | Reviews assigned Evaluations, can return work for changes, confirms license/verification items. |
| Evaluator | Performs and submits assigned Evaluation scoring. |
Every area is gated by role permission — a Reviewer or Evaluator account attempting an Administrator-only action (such as managing Internal Users) is denied regardless of the URL used.
Internal Payment Operations gives Administrators visibility and safe recovery tools over every payment:
Current official contact channels:
| Channel | Value |
|---|---|
| info@plocal.com | |
| Phone | 888-988-3213 |
Public Contact and Support forms submit into the same internal Support Case system. Every submission is durably recorded and reviewable by Administrators from Internal Support Cases, with a status workflow (for example, received → in review → resolved).
The Business Profile's "Report a concern" link routes a visitor to the official Contact page, which submits into this same Support Case system — there is a single, consistent path for any public concern or fraud report, rather than a separate parallel system.

Public Blog index at /blog. Captured from the local development environment.
Only published articles are ever publicly reachable at /blog or
/blog/{slug}. A draft or unpublished article is never exposed on the public site.
Active advertisers appear in the homepage sponsor carousel, linking only to their own
website_url.
PLOCAL uses MySQL as its single source of truth. The conceptual relationships below matter more for day-to-day operation than the full column-level schema.
| Area | Holds |
|---|---|
| Internal Users | Staff accounts and their assigned roles/permissions. |
| States / Locations | The 50-state reference set and dynamically provisioned cities. |
| Categories | The current product category taxonomy. |
| Blog Articles | Editorial content and publication status. |
| Advertisers | The homepage sponsor carousel entries. |
Migrations under backend/database/migrations are the authoritative schema
history; they should be applied in order and never edited after being run against a live
database.
| System | Who | Notes |
|---|---|---|
| Public | Any visitor | No authentication required for browsing pages. |
| Business | An activated Business's authorized user | /business/login, invitation-based onboarding only. |
| Internal | PLOCAL staff | /internal/login, role-based access control. |
Keeping Business and Internal authentication completely separate ensures a compromised or misused Business account can never reach staff-only tooling, and vice versa.
APP_DEBUG=false, HTTPS enabled, and secrets supplied
only through environment configuration — never committed to the repository. PLOCAL does not
claim a formal security certification and is not represented as immune to attack; the controls
above reflect current, verified application behavior.
| Content | Visibility | Notes |
|---|---|---|
| Submission evidence (website screenshots) | Private | Served only through an authenticated/controlled Laravel route — never a direct public file path. |
| Blog cover images | Public | Served through a dedicated media route. |
| Advertiser logos | Public | Served through a dedicated media route. |
The current production design sends transactional email through Google Workspace SMTP, from info@plocal.com, using Laravel's standard Mailer.
# Production .env placeholders
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=info@plocal.com
MAIL_PASSWORD=<GOOGLE_WORKSPACE_SMTP_CREDENTIAL>
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=info@plocal.com
MAIL_FROM_NAME=PLOCAL
Never place real credentials in source control. See Chapter 33 for the Google Workspace setup procedure.
Local development may run with QUEUE_CONNECTION=sync, meaning queued work
executes immediately, inline, with no separate worker process. This is convenient for
development but has no retry/backoff behavior.
Production should use a persistent queue driver (the database driver is supported out of the box) together with a supervised worker process:
php artisan queue:work \ --queue=default \ --tries=3 \ --backoff=60,300 \ --max-time=3600
This worker command should run continuously under a process manager — systemd,
Supervisor, or an equivalent tool already standard on the chosen server — so it is automatically
restarted if it stops. The specific supervisor is an infrastructure choice for the owner/hosting
administrator; this guide does not mandate one.
The table below lists the environment variables PLOCAL reads, verified against the current configuration. No secret values are shown — use the placeholders when configuring production.
# Application APP_NAME=PLOCAL APP_ENV=production APP_KEY=<GENERATED_BY_ARTISAN_KEY_GENERATE> APP_DEBUG=false APP_URL=https://www.plocal.com # Logging LOG_CHANNEL=stack LOG_STACK=single LOG_LEVEL=error # Database DB_CONNECTION=mysql DB_HOST=<PRODUCTION_DB_HOST> DB_PORT=3306 DB_DATABASE=<PRODUCTION_DB_NAME> DB_USERNAME=<PRODUCTION_DB_USERNAME> DB_PASSWORD=<PRODUCTION_DB_PASSWORD> # Session & Cache SESSION_DRIVER=database SESSION_LIFETIME=120 SESSION_ENCRYPT=true CACHE_STORE=database # Queue QUEUE_CONNECTION=database # Mail MAIL_MAILER=smtp MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USERNAME=info@plocal.com MAIL_PASSWORD=<GOOGLE_WORKSPACE_SMTP_CREDENTIAL> MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=info@plocal.com MAIL_FROM_NAME=PLOCAL # Stripe STRIPE_SECRET_KEY=<STRIPE_LIVE_SECRET_KEY> STRIPE_WEBHOOK_SECRET=<STRIPE_LIVE_WEBHOOK_SECRET>
The steps below describe a conceptual deployment to a Linux VPS. No specific hosting provider is assumed.
composer.json).backend/public as the document root..env file (Chapter 27).php artisan key:generate.composer install --no-dev --optimize-autoloader.storage and bootstrap/cache.php artisan migrate --force.php artisan plocal:bootstrap-production.# Install production dependencies composer install --no-dev --optimize-autoloader # Apply the database schema php artisan migrate --force # Seed the production-safe reference data (RBAC, Evaluation policy, categories, 50 states) php artisan plocal:bootstrap-production # Cache configuration, routes, and views for performance php artisan config:cache php artisan route:cache php artisan view:cache # Read-only check of production configuration health php artisan plocal:verify-production-readiness # Start the queue worker (run under a process supervisor) php artisan queue:work --queue=default --tries=3 --backoff=60,300 --max-time=3600
.env or route/view files change after the first deployment, re-run the
config:cache, route:cache, and view:cache commands so
the change actually takes effect.
| Command | Why Not |
|---|---|
migrate:fresh | Drops every table and rebuilds the schema from nothing — destroys all production data. |
db:wipe | Drops every table, view, and type — irreversible data loss. |
plocal:seed-contextual-nav-demo | Creates local-only visual demo fixtures, not real product data. |
plocal:internal-demo-evaluation:create / :cleanup | Creates/removes a private local workspace demo Evaluation. |
plocal:run-professional-rating-demo | A synthetic demo workflow intended only for the isolated test database. |
plocal:verify-dallas-demo, plocal:verify-multi-market-demo | Verify local-only demo fixtures; meaningless (or unsafe) against production data. |
plocal:backfill-legacy-market-membership (without --dry-run first) | Writes new rows for existing Businesses — always review with --dry-run before running for real. |
This list reflects the current Artisan command registry at the time of writing.
Command names should be re-verified against php artisan list before each
deployment, since the registry can change over time.
php artisan plocal:bootstrap-production is the single production-safe seeding
entry point. It is idempotent — running it again on an already-initialized database changes
nothing.
.env.https://www.plocal.com/webhooks/stripe (confirm the exact current route before
finalizing).STRIPE_WEBHOOK_SECRET.The Stripe CLI listener (stripe listen) is never used in production — see
Chapter 10.
info@plocal.com.MAIL_* variables from Chapter 25 in the production
.env.Exact Google Workspace account settings are specific to the owner's account and are not reproduced here.
The web server's document root must point at backend/public — never at the
repository root, and never directly at backend/ itself. Only the contents of
public should ever be reachable directly over the web.
/path/to/project/backend/publicindex.php (Laravel's standard front
controller pattern).www or non-www) and redirect the
other consistently.APP_URL to the exact canonical HTTPS domain.This guide uses https://www.plocal.com as the expected canonical
domain; confirm and adjust if the final configured domain differs.
| Symptom | Likely Cause | Where to Check | Safe Action |
|---|---|---|---|
| 500 error | Uncaught exception | storage/logs/laravel.log | Read the stack trace; fix the underlying cause, never disable error visibility to hide it. |
| Database connection error | Wrong credentials or DB unreachable | .env DB_* values, MySQL service status | Verify credentials and that MySQL is running and reachable. |
| Payment stays pending | Webhook not received | Stripe dashboard webhook delivery log | Confirm the endpoint is reachable over HTTPS; see Chapter 41. |
| Activation did not start | Activation step failed after payment | Internal Payment Operations | Use Retry Activation; never edit payment rows directly. |
| Email not sending | SMTP misconfiguration | .env MAIL_* values, mail logs | Send a test email; verify SMTP credentials. |
| Queue not processing | Worker not running | Process supervisor status, failed_jobs table | Restart the worker; inspect failed jobs. |
| Image missing | Broken path or missing storage symlink | Browser network tab, storage configuration | Confirm the file exists and the serving route is correct. |
| Business not in Find Businesses | Not yet discovery-eligible | Internal Business record status | See Chapter 42 (Directory Troubleshooting). |
| Permission / RBAC issue | Account role lacks the needed permission | Internal Users role assignment | Confirm the account has the correct role. |
Walk this chain top to bottom in Internal Payment Operations. If the webhook never arrived, fix delivery/HTTPS/firewall first — retrying activation cannot help until the webhook is actually received. If the webhook arrived and payment is confirmed but activation did not complete, use Retry Activation.
Symptom: a Business was submitted and paid for, but does not appear in Find Businesses.
Check, in order:
If a Business is stuck ineligible purely due to a geography reference-data gap, the safe reconciliation command is:
php artisan plocal:reconcile-discovery-eligibility --dry-run
Review the dry-run output first, then re-run without --dry-run only once the
proposed change looks correct.
failed_jobs table.PLOCAL includes an automated Laravel test suite covering the majority of product behavior, run against a fully isolated test database.
--env=testing flag alone to guarantee isolation. Tests must never
be allowed to mutate the real development or production database.
# Run the full suite php artisan test # Run a focused subset php artisan test --filter="SomeTestName"
cd backend composer install # configure .env for local development # create the local database php artisan migrate php artisan plocal:bootstrap-production php artisan serve
For local Stripe testing:
stripe listen --forward-to http://localhost:8000/webhooks/stripe
If queued work needs to be processed locally:
php artisan queue:work
Never place real production credentials in a local .env file.
A short answer to: "PLOCAL is already deployed — how do I actually use it?"
.env configuredAPP_DEBUG=falseAPP_URL correct and HTTPSplocal:bootstrap-production complete| Window | Monitor |
|---|---|
| First hour | HTTP error rate, Stripe webhook delivery, first real payment/activation. |
| First day | Mail delivery, queue job processing, disk usage, database health, support inbox. |
| First week | Search-engine indexing, backup completion, ongoing support volume, overall stability. |
Items above are operational recommendations, not application behavior claims.
PLOCAL is prepared for handoff. The items below are genuinely non-blocking follow-ups identified during the most recent technical review — none of them prevent a production launch.
/pro/{slug}.| Area | Route |
|---|---|
| Public | / |
| Public | /find-businesses |
| Public | /submit-your-business |
| Public | /recognition |
| Public | /blog |
| Business | /business/login |
| Internal | /internal/login |
| Internal | /internal |
Action-only (POST) endpoints are intentionally omitted from this reference.
The following are infrastructure and configuration responsibilities for the owner or hosting administrator — they are not missing application features.
| Responsibility | Owner Action |
|---|---|
| Hosting / VPS | Provision and maintain the server. |
| Domain / DNS | Own and configure the production domain. |
| HTTPS | Obtain and renew a valid certificate. |
| Production database credentials | Provision and secure production MySQL access. |
| Stripe LIVE credentials | Provide the live secret key and webhook secret. |
| Stripe Tax registrations | Confirm applicable tax jurisdictions, if used. |
| Google Workspace SMTP credentials | Provide the sending mailbox credential. |
| Backup infrastructure | Implement scheduled, tested backups. |
| Queue supervisor | Keep the queue worker running continuously. |
| Server monitoring | Monitor uptime, disk, and error rates. |
This guide reflects the PLOCAL codebase as verified at the time of writing: routes, Artisan commands, environment variables, the Digital / Service / Trust Evaluation model, the 50-state directory architecture, the Stripe payment flow, the Business Portal, the Internal Portal, the Blog, Advertisers, and the production bootstrap command were each checked directly against the current source.