Skip to content

Architecture

System Topology

graph TB
    subgraph Clients
        Browser["Browser / Mobile"]
    end

    subgraph Server["Laravel 10 + Octane v2 (persistent workers)"]
        CS["CustomSession MW\nnamesapced cookies\n{name}_user / {name}_admin"]
        WEB["Web Surface\nuser middleware group\nInertia + Vue3"]
        ADMIN["Admin Surface\nweb+admin group\nBlade + Inertia"]
        API["API Surface\napi group\n(thin, Sanctum stub)"]
        WEBHOOK["Webhook Surface\nno auth, no CSRF\nSBPS + Atone"]
    end

    subgraph DomainLayer["Domain Layer (app/Modules/)"]
        CTL["Controllers\nthin delegation"]
        ACTION["Actions\nCQRS single-ops\nspatie DTO input"]
        MGR["Managers\nstateful coordinators"]
        QB["QueryBuilders\n9 custom Eloquent builders"]
    end

    subgraph Data
        MYSQL["MySQL"]
        REDIS["Redis\ncache + queue"]
    end

    subgraph Scheduler["Octane Scheduler"]
        PJ["PublishJob @1min"]
        LJ["LotteryWinnerJob @1min"]
        OJ["OriconJob @22:00 JST"]
        BJ["BillboardJob @22:00 JST"]
    end

    Browser --> CS
    CS --> WEB
    CS --> ADMIN
    Browser --> API
    Browser --> WEBHOOK

    WEB --> CTL
    ADMIN --> CTL
    API --> CTL
    WEBHOOK --> CTL

    CTL --> ACTION
    CTL --> MGR
    ACTION --> QB
    MGR --> QB
    QB --> MYSQL
    ACTION --> REDIS
    MGR --> REDIS

    PJ --> MYSQL
    LJ --> MYSQL
    LJ --> REDIS
    OJ --> SFTP["Oricon SFTP"]
    BJ --> FTP_BIL["Billboard FTP"]

Application Style

Modular monolith. Domain logic lives in app/Modules/ (11 modules). Modules self-register via their own Providers/ subdirectory. No nwidart/laravel-modules — modules are plain PHP namespaces, not packages.

No dedicated Services/ layer. Service logic replaced by: - Actions/ — single-responsibility CQRS units (stateless, accept DTOs) - Managers/ — stateful coordinators for multi-step operations - Interfaces/ + Providers/ — contract/implementation binding per module

Modules

Module Class Core Purpose
Product CORE Catalog: artists, products, variations, pricing, CKC codes
Order CORE Purchase flow, order state, fulfilment, reporting exports
Payment CORE Payment provider facade (SBPS + Atone)
Lottery CORE Lottery application, winner selection, winner notification
LotteryCart CORE Lottery application staging area
Cart CORE Standard shopping cart (guest + authenticated)
Membership CORE User auth, profile, address, CKC account linking
Sales CORE Revenue aggregation, label views, Oricon/Billboard exports
Chekicha CORE CKC serial code import and distribution
Maps SUPPORT Postcode → address lookup (Google Maps)
Inquiry SUPPORT Customer inquiry form + auto-reply

Cross-Module Dependencies

graph LR
    MEMBERSHIP["Membership\nUser + Auth"] --> CART["Cart"]
    MEMBERSHIP --> LOTTERY_CART["LotteryCart"]
    CART -->|"checkout"| ORDER["Order"]
    LOTTERY_CART -->|"draw result"| LOTTERY["Lottery"]
    LOTTERY -->|"winner"| ORDER
    ORDER --> PAYMENT["Payment\nSBPS / Atone"]
    ORDER --> CHEKICHA["Chekicha\nCKC codes"]
    PRODUCT["Product\nArtist + Variation"] --> CART
    PRODUCT --> LOTTERY_CART
    PRODUCT --> CHEKICHA
    ORDER --> SALES["Sales\nread-only aggregate"]
    MAPS["Maps\nGoogle Maps"] --> MEMBERSHIP

Purchase flow switch: Product.sale_type enum determines routing: - normal → Cart flow → Order → SBPS/Atone - lottery → LotteryCart flow → Lottery draw → Order (winners only)

HTTP Surfaces

Three separate access contexts, all going through CustomSession middleware for cookie namespacing:

Surface Routes Guard UI
Web routes/web/ (5 files) web (User) Inertia + Vue3
Admin routes/admin/ (6 files) admin (Admin) Blade + Inertia
API routes/api.php Sanctum (unused) JSON
Webhooks routes/webhook.php None JSON

System Hotspots

Complexity

  • Order fulfilment: DB transaction with lockForUpdate on variations + ckc_codes + external CkcConnect::attach() call inside the transaction → external API latency holds DB locks
  • Lottery winner selection: concurrent applications, GROUP_CONCAT truncation risk, idempotency gap on is_sent flag
  • Dual payment provider: SBPS (6 methods, redirect-based) + Atone (inline, BNPL) with separate webhook reconciliation

Active Change Areas (from git log)

  • Lottery — 3 of last 5 commits; SendLotteryWinnerEmailJob has uncommitted changes on staging
  • Data migrations — bulk upsert + chunk-size tuning ongoing in database/migrations/data_migration/
  • HasDownloadFile — recently reimplemented
  • Admin Users — new routes and views added

Risk Signals

  • SendToOricon swallows all Throwable → silent FTP failures
  • SBPS webhook has no inbound HMAC verification
  • CkcConnect::attach() inside DB transaction → Chekicha API downtime = order rollback
  • Zero automated test coverage across all 11 modules (placeholder PHPUnit scaffold only)
  • Soft deletes not on Order — order hard-delete is irreversible

Critical Dependencies

Dependency Risk
SBPS gateway All normal purchases blocked if down
Chekicha API Order fulfilment rolls back on API failure
MySQL Single DB, no read replica visible
Redis Cart session + queue; if down, cart broken and notifications fail
spatie/laravel-data v3 DTOs used platform-wide; major version bump = mass breakage
Octane v2 Persistent workers; Config::set() mutation in CustomSession risks cross-request bleed