brandessence / backend-prd / v1.0 Laravel 13 · PHP 8.3 · PostgreSQL · Redis · Filament 5
Backend Developer Reference

Brandessence Backend
Technical PRD

Domain: app.brandessence.com + admin.brandessence.com Build: 28 blocks · ~22 weeks DB: ~50 tables · PostgreSQL + pgvector
Laravel 13 PHP 8.3 PostgreSQL + pgvector Redis Filament 5 Livewire 4 OpenLiteSpeed VPS Cloudflare R2 Typesense ZeptoMail
§ 1

Architecture Overview

One Laravel application. One PostgreSQL database. Two Filament panels. All tables live together with proper foreign key constraints. pgvector enabled from day one for future AI features.

Panel 1 — Customer Portal

  • app.brandessence.com
  • Filament customer panel
  • Public register / login via OTP
  • Purchase reports + manage licenses
  • Livewire reactive checkout
  • Dashboard, downloads, samples

Panel 2 — Admin + CRM

  • admin.brandessence.com
  • Single panel, role-based nav
  • Super Admin → everything
  • Report Author → own reports
  • Sales Manager → CRM sections
  • Research Analyst → tickets only

REST API

  • /api/* — public endpoints
  • No auth required
  • Consumed by Next.js frontend
  • Cached at Cloudflare edge
  • Reports, catalogue, form submissions
  • Cache-Control headers on all GET
Why one combined Admin + CRM panel? Admin and CRM share the same crm_users table, same role system, same auth, and frequently reference the same data. A Sales Manager creating a manual order needs admin order management. One panel. Role-based sidebar. Clean and maintainable.
§ 2.1

OTP Authentication

OTP-Only. No passwords anywhere in the system. Email → OTP → session. 6-digit code, 10-minute TTL, stored in Redis. After 3 failed attempts OTP is invalidated.

OTP Flow

1
Email entered

Check if email exists in users table. New or existing — both proceed to OTP generation.

2
OTP generated & stored in Redis

Key: otp:{email} — Value: {code, attempts: 0} — TTL: 600 seconds. Any existing OTP invalidated.

3
Send via ZeptoMail

Dispatched as SendOtpEmail queue job. User sees OTP input + profile fields (first purchase) or OTP only (returning).

4
OTP validated

Valid → create session, redirect to intended page. Invalid → increment attempts. 3 failures → invalidate, must request new.

5
Session created

Redis session driver. 30-day duration with activity. Max 2 concurrent sessions (1 desktop + 1 mobile). 3rd login = oldest terminated. Device detected via User-Agent.

First-Time (During Checkout)

Required: email, first_name, last_name, company, phone, country. Optional: job_title. Account created with ALL fields simultaneously after OTP verification. No separate registration-then-checkout flow.

First-Time (Sample Access)

Only email required. OTP verifies. Account created with email only. Profile fields filled later from dashboard if they choose.

§ 2.2

Cart System

Database-backed cart for logged-in users. One cart per user. Cart items reference reports. Prices always pulled from the database — never from query parameters or frontend.

Schema

carts
idPK
user_idFK users
coupon_idFK coupons nullable
created_at / updated_attimestamp
cart_items
idPK
cart_idFK carts
report_idFK reports
purchase_typeenum: full_report | chapter | multi_chapter
license_typeenum: single_user | multi_user | enterprise
chapter_idsJSON array nullable
include_excelboolean default false
include_pptboolean default false

Coupon Validation — 6 Checks (in order)

1
Code exists?

"Invalid coupon code"

2
Is active / not expired?

"This coupon has expired"

3
Max usage reached?

"This coupon is no longer available"

4
User already used it?

Check coupon_usage table — "You've already used this coupon"

5
Meets minimum order value?

"Minimum order of $X required"

6
Applicable to cart items?

Scope: all / specific_industries / specific_reports — "This coupon doesn't apply to items in your cart"

§ 2.3

Checkout Flow

Multi-step Livewire component. Each step validates before allowing progression to the next.

Step 1

Auth Check
Redirect to OTP login with return URL if unauthenticated.

Step 2

Order Review
Prices recalculated from DB. Coupon field. Show breakdown: Subtotal / Discount / Total.

Step 3

License + Billing
Licensee name, company, billing address, GST (required if India).

Step 4

Gateway Select
CCAvenue (Indian) or PayPal (International).

Step 5

Payment
Create order (pending), process via gateway, verify, dispatch OrderCompleted event.

Step 6

Confirmation
Redirect to /order-confirmation/{order_number} with download links.

Anti-Tampering — Critical: The order total is ALWAYS recalculated from database prices at the moment of payment verification. NEVER trust amounts from the frontend, from the gateway callback, or from session/cache. Any mismatch → flagged_review status → DO NOT fulfill → alert PM immediately.

OrderCompleted Event Listeners

  • CreateLicenseRecords — creates license record per order item with type, user, report, expiry
  • DispatchWatermarkJob — queues PDF watermarking per report. Text: {Name} | {Company} | {OrderNum} | {Date} | Confidential
  • SendOrderConfirmationEmail — ZeptoMail with order details + download links (or "processing")
  • UpdateCRMLead — if buyer email matches a lead → move to "Won", record order_id, attribute revenue
  • SyncTypesenseIndex — update report's purchase_count for popularity sort
  • LogAnalyticsEvent — store purchase event for admin dashboard

CCAvenue Integration

1
Build request params

order_id, amount, currency (INR), redirect_url, cancel_url, merchant_id

2
Generate checksum & encrypt

Server-side only using CCAvenue working key. NEVER expose working key to frontend.

3
Redirect to CCAvenue

User completes payment on hosted page.

4
On return (redirect_url)

Decrypt response → verify order_id + amount match DB → set status accordingly.

5
Server-to-server callback (status_url)

CCAvenue sends callback regardless of user redirect. Handles browser-close edge case. Ignore if already completed.

PayPal Integration

1
Load PayPal JS SDK

Livewire component renders PayPal button inline.

2
createOrder endpoint

POST /api/paypal/create-order — Laravel calls PayPal /v2/checkout/orders, returns PayPal order ID to SDK.

3
User approves in popup

SDK calls POST /api/paypal/capture-order.

4
Capture + verify

Laravel calls PayPal capture API → verify amount matches DB total → dispatch OrderCompleted event or flag.

§ 2.4

License System

licenses
idPK
user_idFK users
order_idFK orders
report_idFK reports
license_typeenum: single | multi | enterprise
licensee_namestring
download_limitint nullable
download_countint default 0
chaptersJSON array nullable
includes_excelboolean
includes_pptboolean
watermark_textpre-computed string
expires_attimestamp nullable
is_activeboolean default true
Single User

One named user (purchaser). Unlimited re-downloads. Watermark: Name | Company | OrderNum | Date | Confidential

Multi-User

Company-level. N downloads (admin configures per report, e.g. 5 or 10). Counter only — no seat tracking. Limit reached → "Contact support."

Enterprise

Organisation-level. Unlimited downloads. No seat tracking. Watermark includes "Enterprise License".

ReportAccessService::checkAccess()

// Access check priority order:
1. No user → preview only
2. Direct purchase license → full access (check is_active + expiry)
3. Industry subscription → full access (check pivot + expiry)
4. Neither → preview only

// Called by:
 Report detail API endpoint (how much data to return)
 Download controller (allow download?)
 Customer dashboard (which reports in "My Reports")
§ 2.5

Secure File Delivery

R2 Bucket Structure

/reports/{report_id}/original.pdf           ← master, never served directly
/reports/{report_id}/excel.xlsx
/reports/{report_id}/ppt.pptx
/reports/{report_id}/watermarked/{license_id}.pdf  ← per-license
/samples/{ticket_id}/sample.pdf

Download Flow

1
User clicks "Download PDF"

Controller calls ReportAccessService::checkAccess()

2
Access denied

Return 403 — "You don't have access to this report"

3
Check watermarked PDF in R2

Does /watermarked/{license_id}.pdf exist?

4a
PDF exists

Generate signed URL (60-min expiry via S3-compatible API). Increment license.download_count. Log to download_logs. Return URL.

4b
PDF not yet watermarked

Dispatch high-priority watermark job. Return "Your download is being prepared. You'll receive an email shortly."

§ 2.6

Chapter-Level Purchasing

Buyer can purchase individual chapters or multiple. Watermarked PDF generated containing only purchased chapters (extracted from master PDF).

report_chapters
idPK
report_idFK reports
titlestring
chapter_numberint (order)
page_countint
pricedecimal nullable (null = not sold separately)
is_gatedboolean

Cart URL format: app.brandessence.com/cart/add/{report_id}?type=chapter&chapters=5,6,7,8

License chapters field = JSON array of purchased chapter IDs. Access check: does chapters array contain the requested chapter_id?

§ 2.7

Subscription System

Admin-managed only. No public subscription checkout. All subscriptions created by Super Admin or Sales Manager from the admin panel.
subscriptions
idPK
user_idFK users
plan_typeenum: industry_based | report_based | enterprise_all_access
download_limitint nullable
pricedecimal (negotiated annual)
payment_statusenum: paid | invoice_sent | pending
payment_termsenum: immediate | net_15 | net_30 | net_60
starts_at / expires_attimestamp
auto_renewboolean
is_activeboolean
created_byFK crm_users

Renewal Notification Schedule

  • 30 days before — "Your subscription expires in 30 days"
  • 14 days before — "Your subscription expires in 14 days"
  • 7 days before — "Renew now"
  • On expiry — set is_active = false, send "expired" email
  • 7 days after — "We miss you — renew to restore access"
§ 2.8

Customer Dashboard Pages

/dashboard

Summary cards (purchases, active subs, saved, samples received). Recent activity log — last 10 entries.

/dashboard/reports

Grid of all reports via license OR subscription. Download PDF/Excel/PPT. Chapters view — buy remaining. Filter: All | Full | Chapters | Add-ons.

/dashboard/reports/{slug}

Full interactive report viewer — Livewire page. All charts with complete data via ApexCharts. All TOC chapters unlocked. Full segmentation + clickable country map.

/dashboard/samples

Sample deliveries. Signed URL per sample. "Buy Full Report" → cart link.

/dashboard/orders

Purchase history table. Invoice PDF download. Order detail line items.

Profile / Security / Billing

Edit profile. Read-only email. Saved billing addresses. GST number. "Logout all devices" button. Email notification preferences.


§ 3.1 – 3.2

Admin + CRM Panel Structure

Role
Access Scope
Super Admin
Everything — all admin sections + all CRM sections
Platform Admin
Admin only: reports, orders, users, coupons, content, settings. No CRM.
Report Author
Own reports only. No pricing, no orders, no CRM. Eloquent query scoped to author_id.
Sales Manager
All CRM sections + Manual Order Creation
Sales Rep
Own leads only, own pipeline, raise sample requests
Research Manager
All sample requests, assign analysts, workload view
Research Analyst
Assigned sample requests only — upload files, add comments
§ 3.3

Report Management — 12-Tab Form

The most complex admin feature. Filament Resource with tabbed form. All chart data saved as JSON in report_charts table.

Tab 1 Basic Info

title, slug, short_description, full_description (rich text), report_code, publisher_name

Tab 2 Taxonomy

industries (multi-select pivot), geographies (hierarchical), report_type, methodology, forecast_period, base_year

Tab 3 Pricing

single/multi/enterprise prices. enterprise_enquiry toggle. excel_price, ppt_price. multi_user_download_limit.

Tab 4 Chapters

Repeater field. chapter_number, title, page_count, price, is_gated. Drag-to-reorder.

Tab 5 Chart Builder

Live preview for: market size bar, regional pie/donut, CAGR line, market share horizontal bar, segment stacked, interactive country map.

Tab 6 Highlights

market_size_value, market_size_year, cagr_percentage, forecast_period_display, companies_covered

Tab 7 Key Players

Repeater: company_name, logo (R2 upload), hq_country, description. First 5 free / rest gated.

Tab 8 Segmentation

Dimension name + sub_segments per dimension. e.g. "By Type", "By Application".

Tab 9 SEO

seo_title (max 70), meta_description (max 160), canonical_url, og_image, no_index, focus_keywords

Tab 10 FAQs

Repeater: question + rich text answer.

Tab 11 Files

report_pdf (max 100MB → R2), excel_file (50MB, optional), ppt_file (100MB, optional)

Tab 12 Status

enum: draft | published | unpublished | scheduled. published_at timestamp for scheduling.

report_charts table schema

report_charts
chart_typeenum: market_size_bar | cagr_line | regional_pie | market_share_bar | segment_stacked | country_map
titlestring
configJSON (chart-specific config)
dataJSON (actual data rows)
is_visibleboolean
sort_orderint
§ 3.4

Order Management + Manual Orders

Order list with filters for status, gateway, date range, type. CSV export. Manual order creation for Super Admin and Sales Manager allows custom pricing and offline payments.

Manual Order Fields

  • Customer: search existing by email, or create new (email → OTP registration)
  • Items repeater: report, license type, price override (overrides catalogue price), excel/ppt toggles
  • Payment method: Online / Wire Transfer / Cheque / Payment Terms
  • Payment status: Paid (offline) / Invoice Sent / Pending
  • Payment terms: Immediate / Net-15 / Net-30 / Net-60
  • Access grant: Immediate → creates licenses now. On Payment Confirmation → licenses with is_active = false, activated when admin marks paid.
  • CRM link: searchable select to link to lead → moves lead to "Won", attributes revenue
§ 3.5

CRM — Lead Management

13 classification fields per lead. Every action creates an activity log entry building the chronological timeline on lead detail page.

Lead Sources
  • website_sample
  • website_contact
  • website_enterprise
  • website_newsletter
  • manual (admin-created)
  • csv_import
Score Labels

Manually set by sales rep:

hot   warm   cold


Default on creation from website: warm

13 Classification Fields

leads — classification fields
requirement_typesyndicated | custom_research | consulting | subscription | analyst_call | market_sizing
intent_levelactive_evaluation | planning | exploratory | information_only
budget_rangeunder_5k | 5k_15k | 15k_50k | 50k_plus | enterprise
timelineimmediate | short_term | long_term | unspecified
decision_roledecision_maker | influencer | researcher | procurement
use_casemarket_entry | competitive_benchmarking | investment_dd | product_launch | strategic_planning | academic | regulatory
geography_scopeJSON array (country/region codes)
industry_classificationJSON array (industry IDs)
engagement_readinessready | needs_info | comparing | stalled | lost_competitor | not_ready
conversion_probabilityhigh | medium | low
purchase_historynew | previous_12mo | lapsed | existing (auto from users table)
competitive_situationtext (free form)
score_labelhot | warm | cold

Duplicate Detection

On lead creation (form or CSV import): check leads table for matching email. If match found → update existing lead with new data (if fields were empty) → log activity: "Duplicate inquiry merged — {source}" → If sample request: create ticket linked to existing lead.
§ 3.6

CRM — Pipeline Kanban

Livewire Kanban component. Drag-drop between stages updates lead.pipeline_stage_id and creates activity log entry.

New Lead
Inbound form submission
CSV import entry
Contacted
Initial outreach done
Sample Shared
Auto-move on sample delivery
Follow-up
Awaiting response
Proposal Sent
Negotiation
Won ✓
Auto on order complete
Lost ✗
Requires reason (modal)
Auto-stage triggers: Sample shared → auto-move to "Sample Shared". Order completed for lead's email → auto-move to "Won" and link order_id, attribute revenue to sales rep.
§ 3.7

CRM — Sample Request Ticketing

Ticket number format: SR-2026-0001 (auto-generated). 5-stage workflow from submitted to shared.

1
SUBMITTED

Auto from website form or manually by sales rep. Research Manager notified.

2
ASSIGNED

Research Manager sets assigned_to (analyst) and due_date. Analyst notified.

3
IN_PROGRESS

Analyst changes status. Can add comments (page refresh, no real-time WebSocket in Phase 1).

4
READY

Analyst uploads file to R2. New upload replaces previous (old deleted). Sales rep notified.

5
SHARED

"Publish to Dashboard" → creates sample_delivery record, sends OTP access link via email. OR "Send via Email" → manual, mark as shared. Lead auto-moves to "Sample Shared" stage.


§ 4

REST API — Endpoints for Next.js

All GET endpoints are public, no auth required, cached at Cloudflare edge. POST endpoints: no-store.

Reports & Catalogue

GET
/api/reports
Paginated report list. Params: page, per_page, industry, geography, sort (latest|price_asc|price_desc|popular). Cache: 5 min.
GET
/api/reports/{slug}
Full report detail. Chart data is PREVIEW only (truncated to 3-4 points). key_players: first 5 only. TOC: chapter titles + is_gated flag, no gated content. Cache: 15 min.
GET
/api/homepage
featured_reports, testimonials, stats, banners, client_logos, latest_blog_cards. Cache: 30 min.
GET
/api/industries
id, name, slug, icon_url, report_count. Cache: 1 hr.
GET
/api/geographies
Hierarchical regions → countries, each with report_count. Cache: 1 hr.

Form Submissions

POST
/api/leads
Creates lead in CRM. Requires reCAPTCHA token. If source=website_sample: also creates sample_request ticket. Duplicate detection on email. Returns 201.
POST
/api/newsletter
Creates newsletter_subscribers record. Email required + unique. Returns 201.
POST
/api/paypal/create-order
Calls PayPal /v2/checkout/orders. Returns PayPal order ID to JS SDK. Body: {order_id}
POST
/api/paypal/capture-order
Calls PayPal capture. Verifies amount vs DB. Dispatches OrderCompleted or flags. Body: {paypal_order_id, order_id}

§ 5

Queue Jobs & Scheduled Commands

Queue Jobs (Redis-backed)

WatermarkReportPdfWatermarks PDF for a specific license. High priority queue. Source from R2 → FPDI watermark → upload to /watermarked/{license_id}.pdf
SendOtpEmailSends OTP via ZeptoMail API
SendOrderConfirmationEmailOrder details + download links (or "processing" message)
SendSampleReadyEmailNotifies prospect that sample is available on dashboard
SendSubscriptionReminderEmail30/14/7 day renewal reminders + expiry + post-expiry
SyncReportToTypesenseIndex/update a report in Typesense. Dispatched on report CRUD.

Scheduled Commands (Cron via Laravel Scheduler)

// config/console.php or Kernel.php

daily at 00:00  CheckSubscriptionExpiry    ← expire subs, send notifications
daily at 00:30  SendSubscriptionReminders  ← 30/14/7 day warnings
daily at 02:00  ReconcileOrders            ← check orphaned pending orders with gateways
daily at 03:00  ExpireFailedOrders         ← expire pending/failed > 24 hours

Supervisor Workers

# Default queue: 2-4 workers
php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600

# High priority (watermarking + payments): 1 dedicated worker
php artisan queue:work redis --queue=high,default --sleep=3 --tries=3
§ 6

Typesense Indexing

Laravel Scout with Typesense driver. Auto-sync on report CRUD. Full reindex available via php artisan scout:import "App\Models\Report"

Collection: reports — Key Fields

typesense collection: reports
titlestring indexed
short_descriptionstring indexed
industriesstring[] facet
geographiesstring[] facet
regionsstring[] facet
countriesstring[] facet
report_typestring facet
publication_yearint32 facet
single_user_pricefloat
license_typesstring[] facet
purchase_countint32 (popularity sort)
created_atint64 unix timestamp
§ 7

Email Templates — ZeptoMail

1 OTP Email

"Your verification code is {code}. Expires in 10 minutes."

2 Order Confirmation

Order details, items, license type, download links (or "processing" if watermarking still in progress)

3 Sample Ready (Dashboard)

"Your sample for {report} is ready. Click to access." — OTP access link.

4 Sample (Email Delivery)

Sent by sales rep manually with attachment — template for consistency

5–6 Subscription Reminders

30 / 14 / 7 day variants + expiry notification + post-expiry "we miss you" re-engagement

7 Welcome Email

Sent on first registration (after first checkout or first sample OTP)

8 Download Ready

"Your report is ready for download" — sent when async watermarking completes for first download

§ 8

Build Timeline — 28 Blocks

Active development: Weeks 3–25 (~22 weeks). Complexity ratings guide prioritisation and sprint planning.

Block / Description
Complexity
Notes
#1 Project Setup — Laravel scaffold, Filament 2-panel, Redis, R2, folder structure
Low
Foundation
#2 Database — ~50 migrations + seed reference data
Medium
Do early
#3 Auth System — OTP flow, Redis session, 2-session limit
Medium
Blocker for portal
#4 Report API — public Next.js endpoints (list, detail, homepage, industries, geos)
Medium
Frontend blocker
#5 Report Admin — Filament Resource with all 12 tabs including chart builder
High
Largest single block
#6 Content Admin — Industries, banners, testimonials, logos, team, events, careers, inquiries, SEO, settings
Medium
Repetitive CRUDs
#7 Cart + Checkout — cart system, multi-step Livewire, coupon validation
High
Core ecommerce
#8 CCAvenue — checksum, redirect, callback, verification, anti-tamper
High
Security critical
#9 PayPal — JS SDK + createOrder/captureOrder server-side
Medium
Security critical
#10 Order Processing — OrderCompleted event, all 6 listeners, license creation
High
Central logic
#11 License System — ReportAccessService, download limit tracking
Medium
Core access layer
#12 File Delivery — R2 signed URLs, PDF watermarking queue job (FPDI)
High
R2 + PDF lib
#13 Chapter Purchasing — chapter cart/checkout/license/chapter-only PDF delivery
Medium
Extends #7,#12
#14 Subscriptions — admin creation, access checking, expiry, renewal notifications
Medium
Extends #11
#15 Customer Dashboard — all pages (reports, samples, orders, subs, profile, billing, security, notifications)
High
8 pages
#16 Full Report Viewer — Livewire page with ApexCharts, unlocked content
Medium
Charts heavy
#17 CRM Leads — lead model, 13 fields, list view, Kanban, detail page, activity log
High
Complex model
#18 CRM Pipeline — 7-stage Kanban, drag-drop, auto-stage-change triggers
Medium
Livewire DnD
#19 CRM Sample Ticketing — 5-stage tickets, file upload, comments, dashboard delivery
Medium
Workflow heavy
#20 CRM Analytics — simplified Filament stat + table widgets (no chart lib)
Low
Counts + tables
#21 Form-to-CRM — API lead creation with UTM capture, duplicate detection
Low
Extends #17
#22 Manual Orders — admin form with custom pricing, offline payments, CRM link
Medium
Extends #10
#23 Typesense — Scout integration, collection schema, sync on CRUD
Low
Scout driver
#24 Queue Jobs — all background jobs + Supervisor + scheduled commands
Medium
Distributed work
#25 Email Templates — 8 templates via ZeptoMail
Low
Template design
#26 Wishlist — add/remove, dashboard display, login-then-add flow
Low
Simple
#27 User Management — admin user list, detail, role assignment
Low
Filament CRUD
#28 Order Management — admin order list, detail, refund marking
Low
Filament CRUD
Conflicts? This document is the backend developer's primary working reference. Flag any conflicts with FRD v2.0, Project Engagement Document v3, or PRD v1 immediately to the PM.