86 lines
4.7 KiB
Markdown
86 lines
4.7 KiB
Markdown
# 03 — API Contract
|
||
|
||
- Base URL: `/api/v1`
|
||
- Auth
|
||
- Tenant apps: OAuth2 Authorization Code + PKCE, refresh tokens.
|
||
- Super Admin: session-authenticated Filament (web only).
|
||
- Common
|
||
- Pagination: `page`, `per_page` (max 100).
|
||
- Errors: `{ error: { code, message, trace_id }, details?: {...} }`.
|
||
- Rate limits: per-tenant and per-device for tenant apps; 429 with `x-rate-limit-*` headers.
|
||
|
||
Key Endpoints (abridged)
|
||
- Auth: `/oauth/authorize`, `/oauth/token`, `/oauth/token/refresh`.
|
||
- Tenants (Super Admin only): list/read; no create via API for MVP.
|
||
- Events (tenant): CRUD, publish, archive; unique by `tenant_id + slug`.
|
||
- Photos (tenant): signed upload URL, create, list, moderate, feature.
|
||
- Emotions & Tasks: list, tenant overrides; task library scoping.
|
||
- Purchases & Ledger: create purchase intent, webhook ingest, ledger list.
|
||
- Settings: read/update tenant theme, limits, legal page links.
|
||
|
||
Guest Polling (no WebSockets in v1)
|
||
- GET `/events/{slug}/stats` — lightweight counters for Home info bar.
|
||
- Response: `{ online_guests: number, tasks_solved: number, latest_photo_at: ISO8601 }`.
|
||
- Cache: `Cache-Control: no-store`; include `ETag` for conditional requests.
|
||
- GET `/events/{slug}/photos?since=<ISO8601|cursor>` — incremental gallery refresh.
|
||
- Response: `{ data: Photo[], next_cursor?: string, latest_photo_at: ISO8601 }`.
|
||
- Use `If-None-Match` or `If-Modified-Since` to return `304 Not Modified` when unchanged.
|
||
|
||
Webhooks
|
||
- Payment provider events, media pipeline status, and deletion callbacks. All signed with shared secret per provider.
|
||
|
||
## Purchase Wizard Endpoints (Marketing Flow)
|
||
|
||
These endpoints support the frontend purchase wizard for package selection, authentication, and payment. They are web routes under `/purchase/` (not `/api/v1`), designed for Inertia.js integration with JSON responses for AJAX/fetch calls. No tenant middleware for auth steps (pre-tenant creation); auth required for payment.
|
||
|
||
### Flow Overview
|
||
1. **Package Selection**: User selects package via marketing page; redirects to wizard with package ID.
|
||
2. **Auth (Login/Register)**: Handle user creation/login; creates tenant if registering. Returns user data and next_step ('payment' or 'success' for free packages).
|
||
3. **Payment**: Create intent/order, complete via provider callback, finalize purchase (assign package, update tenant).
|
||
4. **Success**: Redirect to success page; email welcome if new user.
|
||
|
||
Error Handling:
|
||
- 422 Validation: `{ errors: { field: ['message'] }, message: 'Summary' }` – display in forms without reload.
|
||
- 401/403: `{ error: 'Auth required' }` – show login prompt.
|
||
- 500/Other: `{ error: 'Server error' }` – generic alert, log trace_id.
|
||
- Non-JSON (e.g., 404): Frontend catches "unexpected end of data" and shows "Endpoint not found" or retry.
|
||
|
||
All responses: JSON only for AJAX; CSRF-protected.
|
||
|
||
### Endpoints
|
||
|
||
- **POST /purchase/auth/login**
|
||
- Body: `{ login: string (email/username), password: string, remember?: boolean }`
|
||
- Response (200): `{ status: 'authenticated', user: { id, email, name, pending_purchase, email_verified }, next_step: 'payment', needs_verification: boolean }`
|
||
- Errors: 422 `{ errors: { login: ['Invalid credentials'] } }`
|
||
|
||
- **POST /purchase/auth/register**
|
||
- Body: `{ username, email, password, password_confirmation, first_name, last_name, address, phone, privacy_consent: boolean, package_id?: number }`
|
||
- Response (200): `{ status: 'registered', user: { ... }, next_step: 'payment'|'success', needs_verification: boolean, package?: { id, name, price, type } }`
|
||
- Errors: 422 `{ errors: { email: ['Taken'], password: ['Too weak'] } }`; creates tenant/user on success.
|
||
|
||
- **POST /purchase/stripe/intent** (auth required)
|
||
- Body: `{ package_id: number }`
|
||
- Response (200): `{ client_secret: string, payment_intent_id: string }`
|
||
- Errors: 422 `{ errors: { package_id: ['Invalid'] } }`
|
||
|
||
- **POST /purchase/stripe/complete** (auth required)
|
||
- Body: `{ package_id: number, payment_intent_id: string }`
|
||
- Response (200): `{ status: 'completed' }`
|
||
- Errors: 422 `{ errors: { payment: ['Not succeeded'] } }` – finalizes purchase.
|
||
|
||
- **POST /purchase/paypal/order** (auth required)
|
||
- Body: `{ package_id: number }`
|
||
- Response (200): `{ order_id: string, status: 'CREATED' }`
|
||
- Errors: 422 `{ error: 'Order creation failed' }`
|
||
|
||
- **POST /purchase/paypal/capture** (auth required)
|
||
- Body: `{ order_id: string, package_id: number }`
|
||
- Response (200): `{ status: 'captured' }`
|
||
- Errors: 422 `{ error: 'Capture incomplete' }` – finalizes purchase.
|
||
|
||
- **POST /purchase/free** (auth required)
|
||
- Body: `{ package_id: number }`
|
||
- Response (200): `{ status: 'assigned' }`
|
||
- Errors: 422 `{ errors: { package_id: ['Not free'] } }` – assigns for zero-price packages.
|