Skip to content

alihamzahq/pulsepass-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

72 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pulsepass API

Laravel 12 backend for a ticket resale platform — Ticketmaster sync, organizer listings, state-machine offers, Stripe-gated quotas.

CI PHP 8.4 Laravel 12 License: MIT

📖 Live API documentation: https://alihamzahq.github.io/pulsepass-api/ (rendered via Redoc, redeployed on every push to main)

What it is

The hard part of a ticket resale marketplace isn't the CRUD — it's what happens the instant a seller accepts an offer. Pulsepass models that moment as a real state machine: accepting one offer flips the listing to sold and atomically auto-rejects every other pending bid on the same listing, all inside a single DB transaction. Around that core sits a nightly Ticketmaster sync with per-event transactions, Stripe subscriptions that gate per-tier listing quotas (Free=1 / Pro=10 / Festival=∞), and an idempotent webhook layer that survives Stripe's retry storms.

Same shape as StubHub, Vivid Seats, or SeatGeek — pared down to the backend pieces that demonstrate senior-tier engineering.

Where to start reading

Three files capture the most interesting decisions:

Highlights

  • Modular monolith — five business domains live under app/Domain/, thin HTTP adapters under app/Http/{Controllers,Requests}/<Domain>/.
  • State machine for offers — five states (SentAccepted | Rejected | Withdrawn | Expired) with guarded transitions via spatie/laravel-model-states. Accepting an offer flips the listing to sold and auto-rejects all sibling offers in one transaction.
  • Idempotent Stripe webhooks — every webhook event ID is recorded in a dedup table; replays are no-ops.
  • Per-event transactions in the Ticketmaster sync — one bad event cannot abort the whole batch; partial-success is the design goal.
  • Tiered listing quotas via Stripe Cashier — Free (1 active listing), Pro (10), Festival (unlimited). Quota is enforced at POST /listings by reading the user's current subscription.
  • 27 documented endpoints — auto-generated OpenAPI 3.1 spec via dedoc/scramble, published to GitHub Pages with Redoc on every push to main.
  • 113 Pest tests, PHPStan level 8, Laravel Pint — enforced in CI.
  • k6 load test for the offer-create hot path — illustrative single-machine run holds p95 < 500ms at 50 concurrent VUs (see scripts/loadtest/).
  • Laravel Telescope in dev only — gated to APP_ENV=local via the framework's official local-only install pattern; never loads in CI or production.

Architecture

flowchart LR
    client[Mobile / Web Client]
    api[Pulsepass API<br/>Laravel 12 + PHP 8.4]
    mysql[(MySQL 8)]
    redis[(Redis<br/>cache + queue)]
    stripe[Stripe]
    tm[Ticketmaster<br/>Discovery API]

    client -->|HTTPS<br/>Bearer token| api
    api --> mysql
    api --> redis
    api -->|Cashier SDK| stripe
    stripe -->|webhooks| api
    api -->|nightly sync<br/>rate-limited| tm
Loading

Full data model and offer state machine diagrams live in docs/architecture.md. Architectural decisions are recorded in docs/adr/ — five short ADRs covering the modular monolith layout, the choice of state-machine library, per-event sync transactions, Sanctum over JWT, and plain Action classes.

Tech stack

Layer Choice
Framework Laravel 12
Language PHP 8.4
Database MySQL 8
Cache & queue Redis
Auth Laravel Sanctum (personal access tokens)
Payments Laravel Cashier (Stripe)
State machine spatie/laravel-model-states
OpenAPI generation dedoc/scramble
Test framework Pest
Static analysis PHPStan level 8
Code style Laravel Pint
Local orchestration Docker Compose
CI GitHub Actions
Load testing k6

Local development

Requirements: Docker and Docker Compose. Nothing else needed on the host.

git clone git@github.com:alihamzahq/pulsepass-api.git
cd pulsepass-api
cp .env.example .env

docker compose up -d
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrate --seed

The API will be reachable at http://localhost:8080. Health check:

curl http://localhost:8080/api/health

Quick auth flow

# Register and capture a Sanctum token
TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/register \
    -H 'Accept: application/json' \
    -d 'name=Demo&email=demo@example.com&password=password123&password_confirmation=password123' \
    | jq -r .token)

# Use the token on any authenticated endpoint
curl http://localhost:8080/api/offers \
    -H "Authorization: Bearer ${TOKEN}" \
    -H 'Accept: application/json'

Tests, lint, static analysis

docker compose exec app composer test     # Pest, 113 tests
docker compose exec app composer lint     # Pint --test + PHPStan level 8
docker compose exec app composer fmt      # Pint, in-place fix

Scheduled commands

Three commands run on the scheduler container:

Schedule Command Purpose
Daily 03:00 events:sync Pull a batch of Ticketmaster events
Every 5 min offers:expire Transition past-due Sent offers to Expired
Daily 04:00 subscriptions:cancel-past-due Cancel subscriptions stuck in past_due beyond a threshold

API documentation

The OpenAPI 3.1 spec is generated automatically from controllers and form requests via dedoc/scramble. Locally:

  • JSON spec: http://localhost:8080/docs/api.json
  • Redoc viewer: http://localhost:8080/docs/api

A GitHub Action publishes the spec to GitHub Pages on every push to main. Once the repo is public, the live docs site is the canonical reference.

Project layout

app/
├── Domain/                    # business core, no HTTP awareness
│   ├── Auth/Actions/          # RegisterUser, LoginUser, LogoutUser
│   ├── Billing/Models/        # Plan, WebhookEvent
│   ├── Events/                # Ticketmaster sync — Models, Jobs, Clients
│   ├── Listings/              # Listing model + status enum
│   ├── Offers/                # Offer model, 5 Actions, 5 State classes
│   └── Organizer/Models/      # Organizer profile
├── Http/
│   ├── Controllers/<Domain>/  # thin HTTP adapters per domain
│   ├── Requests/<Domain>/     # Form Requests per domain
│   └── Resources/             # API Resources (flat)
├── Console/Commands/          # 3 scheduled commands
├── Models/User.php            # Cashier `Billable` lives here
└── Policies/                  # ListingPolicy, OfferPolicy
docs/
├── adr/                       # 5 architecture decision records
└── architecture.md            # 3 Mermaid diagrams
scripts/
└── loadtest/                  # k6 script + seed command + docs

Load testing

A k6 load test for POST /offers is included. Setup, run instructions, and sample results are in scripts/loadtest/README.md.

License

MIT — see LICENSE.

About

Laravel 12 backend for a ticket resale platform — Ticketmaster sync, organizer listings, state-machine offers, Stripe-gated quotas.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages