From fb3818a391b097f6d2ea674e4e23abc7b76d1cd5 Mon Sep 17 00:00:00 2001 From: Anthony Wright Date: Mon, 8 Jun 2026 11:40:05 -0500 Subject: [PATCH] Documentation update --- README.md | 13 ++ apps/api/.env.example | 2 +- apps/api/README.md | 219 ++++++++++++------ apps/api/docs/cqrs.md | 12 +- apps/mobile/.env.staging.example | 5 + apps/mobile/README.md | 161 +++++++++++-- apps/payment/.env.staging.example | 12 +- apps/payment/README.md | 123 ++++++---- apps/payment/docs/consuming-payment-events.md | 37 ++- docs/architecture/api.md | 66 ++++++ docs/architecture/deployment.md | 78 +++++++ docs/architecture/diagrams/flows.md | 55 +++++ docs/architecture/mobile.md | 59 +++++ docs/architecture/overview.md | 105 +++++++++ docs/architecture/payment.md | 63 +++++ docs/environment.md | 67 ++++++ docs/getting-started.md | 181 +++++++++++++++ docs/openspec.md | 38 +++ docs/staging.md | 129 +++++------ docs/troubleshooting.md | 129 +++++++++++ 20 files changed, 1301 insertions(+), 253 deletions(-) create mode 100644 docs/architecture/api.md create mode 100644 docs/architecture/deployment.md create mode 100644 docs/architecture/diagrams/flows.md create mode 100644 docs/architecture/mobile.md create mode 100644 docs/architecture/overview.md create mode 100644 docs/architecture/payment.md create mode 100644 docs/environment.md create mode 100644 docs/getting-started.md create mode 100644 docs/openspec.md create mode 100644 docs/troubleshooting.md diff --git a/README.md b/README.md index 9e50965..b4ac8b4 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,19 @@ This repository is a small monorepo. Run each app from its own folder: - `apps/mobile` - Expo React Native app - `apps/payment` - optional Stripe-backed payment service +## Documentation Map + +- [New developer onboarding](docs/getting-started.md) +- [Central environment variable reference](docs/environment.md) +- [Architecture overview](docs/architecture/overview.md) +- [Mobile architecture](docs/architecture/mobile.md) +- [API architecture](docs/architecture/api.md) +- [Payment architecture](docs/architecture/payment.md) +- [Deployment architecture](docs/architecture/deployment.md) +- [Staging runbook](docs/staging.md) +- [Troubleshooting](docs/troubleshooting.md) +- [OpenSpec workflow](docs/openspec.md) + The local workflow below is Windows PowerShell first because that is the current development environment. macOS/Linux equivalents are mostly the same, except virtual environment activation paths and shell environment-variable syntax. ## Prerequisites diff --git a/apps/api/.env.example b/apps/api/.env.example index bfadd96..c808eab 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -4,7 +4,7 @@ API_PORT=8000 JWT_SECRET=changeme ALLOWED_ORIGINS=* # Local/demo default: keep mock mode unless you intentionally start apps/payment. -PAYMENT_MODE=service +PAYMENT_MODE=mock PAYMENT_SERVICE_BASE_URL= # Stripe should redirect to the mobile/frontend experience, not the API host. # Development/dev client example: diff --git a/apps/api/README.md b/apps/api/README.md index 2c4983b..a91f735 100644 --- a/apps/api/README.md +++ b/apps/api/README.md @@ -1,120 +1,187 @@ # ShoeInn API -## Quickstart (Windows PowerShell) +FastAPI backend for authentication, companies, premium care services, booking holds, appointments, provider/company operations, live updates, notifications, payment reconciliation, and demo seeding. + +## Requirements + +- Python 3.11+ +- Docker Desktop with Compose v2 +- PostgreSQL 15 locally through `docker compose` +- Optional: Stripe/payment service only when `PAYMENT_MODE=service` + +## Quick Start + +From the repository root, the easiest Windows workflow is: + +```powershell +.\scripts\start-api.ps1 ``` -Copy-Item .env.example .env + +That script starts Postgres, creates `apps/api/.venv`, copies and normalizes `.env`, installs dependencies, runs Alembic migrations, starts Uvicorn on `http://localhost:8000`, and seeds demo data unless `-NoSeed` is passed. + +Manual setup: + +```powershell +cd .\apps\api docker compose up -d py -3.11 -m venv .venv .\.venv\Scripts\Activate.ps1 pip install -r ..\..\requirements.txt pip install -e . -alembic upgrade head -python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +Copy-Item .env.example .env ``` -> **Postgres credentials** -> -> The local database created by `make up` uses the default Postgres credentials `postgres` / `postgres`. Update your `.env` -> file only if you have customised the database user or password, and make sure the value matches the connection string in -> `docker-compose.yml`. A mismatch (for example `DATABASE_URL=postgresql+psycopg://shoeinn:shoeinn@localhost:5432/shoeinn` -> while docker-compose still provisions `postgres` / `postgres`) will result in `password authentication failed for user` errors -> when running Alembic migrations. +For a host-run API with Docker Postgres, set: -For Windows host + Docker Postgres, use `localhost` in `DATABASE_URL`. The checked-in `.env.example` uses `db`, which only works from inside the Docker network. +```env +DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/shoeinn +PAYMENT_MODE=mock +``` -If you've previously started the database with a different password, Postgres will keep that credential inside the persisted -volume. You can reset the local database (and remove all data) with: +Run migrations and start: +```powershell +python -m alembic upgrade head +python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ``` + +Health checks: + +```powershell +Invoke-RestMethod http://localhost:8000/health +Invoke-RestMethod http://localhost:8000/ready +``` + +`/health` checks process liveness. `/ready` checks database connectivity, migration head, required notification table, payment mode, and single-instance live-event mode. + +## Database + +`docker-compose.yml` runs only Postgres: + +- Host port: `5432` +- Database: `shoeinn` +- User/password: `postgres` / `postgres` + +If you previously used different credentials, reset the local volume: + +```powershell docker compose down -v docker compose up -d +python -m alembic upgrade head ``` -The `Makefile` in this folder is useful as a reference, but do not assume `make` exists on Windows PowerShell. +Use `localhost` in `DATABASE_URL` when the API runs on the host. Use `db` only from a container on the Compose network. + +## Demo Seed + +Default Shelby County market: -Seed demo data: +```powershell +Invoke-RestMethod -Method Post "http://localhost:8000/dev/seed?reset=true" ``` -Invoke-RestMethod -Method Post http://localhost:8000/dev/seed + +Mt. Juliet market: + +```powershell +Invoke-RestMethod -Method Post "http://localhost:8000/dev/seed?reset=true&demo_market=mt_juliet" ``` -### Availability projection +`reset=true` clears known demo-market records before recreating the selected market. -Confirmed bookings update the `available_slots` read model so clients can query `/slots` without hitting transactional tables. See [docs/cqrs.md](docs/cqrs.md) for an overview of the hold lifecycle, optimistic concurrency checks, and background cleanup that keeps inventory fresh. +Demo credentials are documented in [docs/getting-started.md](../../docs/getting-started.md). -## Curl examples -Register & login: -``` -curl -X POST http://localhost:8000/auth/register -H 'Content-Type: application/json' \ - -d '{"email":"a@a.com","password":"Password1!","role":"customer"}' +## Key Endpoints -curl -X POST http://localhost:8000/auth/login -H 'Content-Type: application/json' \ - -d '{"email":"a@a.com","password":"Password1!"}' -``` -Browse companies: -``` -curl http://localhost:8000/companies -``` +- `GET /health` +- `GET /ready` +- `POST /auth/login` +- `GET /companies` +- `GET /services` +- `POST /appointments` +- `GET /slots` +- `POST /company/appointments/{appointment_id}/claim` +- `WS /live/ws?token=...` +- `POST /push/tokens` +- `POST /webhooks/payments` +- `GET /payments/return/success` +- `GET /payments/return/cancel` -Discover services across companies: -``` -curl "http://localhost:8000/services?city=Austin&query=clean" -``` -The endpoint aggregates active services and returns normalized pricing data: - -```json -[ - { - "id": "SERVICE_ID", - "name": "Basic Clean", - "description": "Quick refresh", - "duration_min": 30, - "price_cents": 1000, - "price": 10.0, - "company": { - "id": "COMPANY_ID", - "name": "Clean Kicks", - "city": "Austin", - "state": "TX", - "postal_code": "73301" - } - } -] +Example login: + +```powershell +Invoke-RestMethod -Method Post "http://localhost:8000/auth/login" ` + -ContentType "application/json" ` + -Body '{"email":"customer@shoeinn.com","password":"Password1!"}' ``` -Optional query parameters: -* `query` – fuzzy match against service or company name -* `city`/`state` – filter by company location -* `company_id` – scope to a specific provider +## Payment Modes -Book appointment: -``` -curl -X POST http://localhost:8000/appointments -H 'Authorization: Bearer TOKEN' \ - -H 'Content-Type: application/json' -d '{"company_id":"ID","type":"pickup",\ - "address":{"line1":"1 Main","city":"Austin","state":"TX","postal_code":"73301"},\ - "start_time_iso":"2025-08-18T15:30:00-05:00"}' +Local development defaults to mock payments: + +```env +PAYMENT_MODE=mock +PAYMENT_SERVICE_BASE_URL= +PAYMENT_MOBILE_REDIRECT_BASE= ``` -Claim appointment (company user): + +Use service mode only when `apps/payment` is running: + +```env +PAYMENT_MODE=service +PAYMENT_SERVICE_BASE_URL=http://localhost:8001 +PAYMENT_MOBILE_REDIRECT_BASE=shoeinn://app ``` -curl -X POST http://localhost:8000/company/appointments/APP_ID/claim -H 'Authorization: Bearer TOKEN' + +For Expo Go return-flow testing: + +```env +PAYMENT_MOBILE_REDIRECT_BASE=exp://:8081/-- ``` -## Tests +The API starts the payment sync worker only when service mode and `PAYMENT_SERVICE_BASE_URL` are configured and `ENABLE_PAYMENT_SYNC_WORKER` is enabled. + +## Workers + +Payment sync: -Focused provider appointment claiming and assignment tests: +- Started from `app.main` on API startup. +- Active only in service payment mode with a configured payment service URL. + +Notification worker: ```powershell -.\venv\Scripts\python.exe -m pytest tests\test_assignment_claiming.py -q +cd .\apps\api +.\.venv\Scripts\Activate.ps1 +python -m app.workers.notification_worker ``` -These tests use in-memory SQLite through `tests/conftest.py`, so no external Postgres, migrations, or seed data are required. +The API process can also run the in-process notification dispatcher when `ENABLE_NOTIFICATION_DISPATCHER=true`. In staging Compose, the API has that disabled and `notification-worker` runs as a separate service. -## Workers +Expired booking holds are cleared by explicit utility/test paths; `app.main` does not currently start a dedicated hold cleanup worker. + +## Tests -- `app.main` starts the payment sync worker only when `PAYMENT_SERVICE_BASE_URL` is configured. -- The notification worker is manual: +Run all backend tests: ```powershell -python -m app.workers.notification_worker +cd .\apps\api +.\.venv\Scripts\Activate.ps1 +python -m pytest tests -q +``` + +Focused tests: + +```powershell +python -m pytest tests\test_assignment_claiming.py -q +python -m pytest tests\test_dev_seed.py -q +python -m pytest tests\test_payment_gateway.py -q ``` -- The current docs in `docs/cqrs.md` mention hold cleanup starting automatically, but `app.main` does not currently start that worker. +The backend test suite uses in-memory SQLite through `tests/conftest.py`, so local Postgres, migrations, and seed data are not required for unit/integration tests. + +## More Documentation + +- [Central environment variable reference](../../docs/environment.md) +- [API architecture](../../docs/architecture/api.md) +- [Deployment architecture](../../docs/architecture/deployment.md) +- [Troubleshooting](../../docs/troubleshooting.md) diff --git a/apps/api/docs/cqrs.md b/apps/api/docs/cqrs.md index ff6c03f..51da855 100644 --- a/apps/api/docs/cqrs.md +++ b/apps/api/docs/cqrs.md @@ -1,11 +1,11 @@ # CQRS and Availability Projection -The booking flow now follows a simple Command/Query Responsibility Segregation pattern: +The booking flow uses a simple Command/Query Responsibility Segregation pattern. -* **Command side** – `/appointments` receives booking requests. The handler places (or reuses) an `appointment_holds` row scoped to `company_id`, `service_id`, and `start_time_utc`. It keeps the write inside a single transaction, verifies that an appointment for the same slot does not already exist, and promotes the hold into a confirmed appointment. A unique index on `appointments(company_id, start_time_utc)` protects against double-booking at the database level. -* **Read side** – confirmed appointments are projected into the `available_slots` read store. Each write sets `is_available` to `false` and stamps `last_booked_at`. Mobile clients can query `/slots` to retrieve available times quickly without replaying command-side logic. -* **Consistency** – any transient hold failures or uniqueness violations bubble up as `409` responses. Tests simulate dueling customers to ensure optimistic concurrency works and that expired holds are cleared before a follow-up booking succeeds. +- **Command side** - `/appointments` receives booking requests. The handler places or reuses an `appointment_holds` row scoped to `company_id`, `service_id`, and `start_time_utc`. It keeps the write inside one transaction, verifies that an appointment for the same slot does not already exist, and promotes the hold into an appointment. A unique index on `appointments(company_id, start_time_utc)` protects against double-booking at the database level. +- **Read side** - confirmed appointments are projected into the `available_slots` read store. Each write sets `is_available=false` and stamps `last_booked_at`. Mobile clients query `/slots` for available times without replaying command-side logic. +- **Consistency** - transient hold failures or uniqueness violations bubble up as `409` responses. Tests simulate dueling customers to verify optimistic concurrency and expired-hold behavior. -Expired holds are deleted by a background thread started in `app/main.py`. The job polls `appointment_holds` on a configurable cadence (`HOLD_CLEANUP_INTERVAL_SECONDS`) so inventory is released automatically if a client abandons checkout. For deterministic validation you can call `app.utils.holds.clear_expired_holds()` directly in scripts or tests. +Expired holds are cleared by explicit utility paths and tests, not by an automatically started worker in `app/main.py`. For deterministic validation or future worker wiring, call `app.utils.holds.clear_expired_holds()` directly. `HOLD_CLEANUP_INTERVAL_SECONDS` is reserved configuration for a future scheduled cleanup loop. -When introducing new consumers, project their booking events into `available_slots` rather than hitting transactional tables directly. This keeps read latency low while letting the command side evolve independently. +When introducing new read-side consumers, project booking events into `available_slots` rather than hitting transactional tables directly. This keeps read latency low while allowing the command side to evolve independently. diff --git a/apps/mobile/.env.staging.example b/apps/mobile/.env.staging.example index e97ba21..2af8a1d 100644 --- a/apps/mobile/.env.staging.example +++ b/apps/mobile/.env.staging.example @@ -2,6 +2,11 @@ # # Use the staging API URL, not localhost. EXPO_PUBLIC_API_URL=https://api-staging.example.com +EXPO_PUBLIC_API_BASE_URL=https://api-staging.example.com +EXPO_PUBLIC_APP_ENV=staging +EXPO_PUBLIC_ENABLE_DEMO_LOGINS=true +EXPO_PUBLIC_DEMO_MARKET=shelby +EXPO_PUBLIC_MOBILE_REDIRECT_BASE=shoeinn://payment-return # Optional for travel-route rendering in staging. EXPO_PUBLIC_GOOGLE_MAPS_API_KEY=replace-me-for-staging diff --git a/apps/mobile/README.md b/apps/mobile/README.md index 32dfe52..8bef8cd 100644 --- a/apps/mobile/README.md +++ b/apps/mobile/README.md @@ -1,50 +1,167 @@ # ShoeInn Mobile -## Expo Go local development +Expo React Native app for customer booking, provider job handling, company admin operations, live appointment updates, notifications, maps, and payment return flows. -For a physical phone in Expo Go, the app must be able to reach both: +## Requirements -1. the Expo Metro server on your computer -2. the ShoeInn API on your computer +- Node.js 20 LTS recommended +- npm +- Expo CLI via `npx expo` +- Expo Go for quick local testing +- Android Studio and an Android emulator, or a physical Android device +- Xcode/iOS Simulator on macOS, or a physical iPhone through Expo Go/development builds +- EAS CLI for preview/development/production builds: `npm install -g eas-cli` -Recommended setup: +Current app stack: + +- Expo SDK `~54.0.35` +- React Native `0.81.5` +- React `19.1.0` + +## Install and Validate + +```bash +cd apps/mobile +npm install +npm run typecheck +npm test -- --runInBand +``` + +Start Expo: + +```bash +npx expo start +``` + +Equivalent npm script: + +```bash +npm start +``` + +## API URL Configuration + +Set both API URL variables for compatibility: ```bash +EXPO_PUBLIC_API_BASE_URL=http://YOUR_API_HOST:8000 +EXPO_PUBLIC_API_URL=http://YOUR_API_HOST:8000 +``` + +Common values: + +- Windows host or iOS Simulator: `http://localhost:8000` +- Android emulator: `http://10.0.2.2:8000` +- Physical phone on LAN: `http://:8000` + +Expo tunnel helps the phone reach Metro, but it does not expose the backend API. Physical devices still need a reachable API URL. + +Windows PowerShell example: + +```powershell +$env:EXPO_PUBLIC_API_BASE_URL="http://192.168.1.14:8000" +$env:EXPO_PUBLIC_API_URL=$env:EXPO_PUBLIC_API_BASE_URL npx expo start --tunnel ``` -If you want to use LAN instead, make sure your phone and computer are on the same Wi-Fi network and set: +The helper script sets these values and checks the API: + +```powershell +.\scripts\start-mobile.ps1 -ApiBaseUrl "http://:8000" +``` + +## Demo Logins and Markets + +Demo login buttons are shown when: ```bash -EXPO_PUBLIC_API_BASE_URL=http://YOUR_COMPUTER_LAN_IP:8000 -EXPO_PUBLIC_MOBILE_REDIRECT_BASE=exp://YOUR_COMPUTER_LAN_IP:8081/-- +EXPO_PUBLIC_ENABLE_DEMO_LOGINS=true ``` -For a dev build or standalone app, use a custom scheme redirect base instead: +Select the visible demo market: + +```bash +EXPO_PUBLIC_DEMO_MARKET=shelby +# or +EXPO_PUBLIC_DEMO_MARKET=mt_juliet +``` + +Shelby County demo accounts use `Password1!`: + +- Customer: `customer@shoeinn.com` +- Provider: `pelham.driver1@shoeinn.com` +- Company admin: `pelham.admin@shoeinn.com` + +Mt. Juliet demo accounts use `Password123!`: + +- Customer: `customer.mtjuliet@shoeinn.demo` +- Provider: `provider.mtjuliet@shoeinn.demo` +- Company admin: `admin.mtjuliet@shoeinn.demo` + +Seed data from the API before logging in: + +```powershell +Invoke-RestMethod -Method Post "http://localhost:8000/dev/seed?reset=true&demo_market=mt_juliet" +``` + +## Maps + +Travel tracking cards use `react-native-maps` and optionally the Google Directions API for route polylines, ETA, and distance: + +```bash +EXPO_PUBLIC_GOOGLE_MAPS_API_KEY=your-google-directions-api-key +``` + +Platform notes: + +- Android needs Google Play services for map tiles on test devices/emulators. +- iOS uses the default Apple Maps renderer unless a Google Maps key is configured in `app.config.ts` and the app is rebuilt. +- Without a Directions API key, map cards can still show markers and fallback copy, but route line/ETA/distance are unavailable. + +## Payment Redirects + +For mock payment mode, no Stripe redirect setup is required. + +For service payment mode with Stripe Checkout, configure the app return base: ```bash EXPO_PUBLIC_MOBILE_REDIRECT_BASE=shoeinn://app ``` -The mobile app now auto-detects the Expo host for local development when `EXPO_PUBLIC_API_BASE_URL` is not set, but explicit LAN configuration is still the safest option for demos. +For Expo Go return-flow testing: -## Google Maps Directions API key +```bash +EXPO_PUBLIC_MOBILE_REDIRECT_BASE=exp://:8081/-- +``` + +The API must also be configured with a matching `PAYMENT_MOBILE_REDIRECT_BASE` or `PAYMENT_RETURN_APP_URL`. -The travel tracking card uses the Google Directions API to render the route polyline, ETA, and distance. +## EAS Builds -1. Create a key in the Google Cloud console with the **Directions API** enabled. -2. Add the key to your environment: +The checked-in `eas.json` defines: + +- `development`: internal development client +- `preview`: internal Android APK preview with demo logins enabled +- `production`: production profile with demo logins disabled + +Commands: ```bash -EXPO_PUBLIC_GOOGLE_MAPS_API_KEY=your-key-here +cd apps/mobile +npx eas build --profile development --platform android +npx eas build --profile preview --platform android +npx eas build --profile preview --platform ios +npx eas build --profile production --platform all ``` -3. Restart the Expo dev server so the env var is available. +The repository uses Expo project id `1a753a1a-ae23-47e9-ba06-cc6148fb36ee` in `app.config.ts`. + +## Troubleshooting -### Optional platform notes +- API unreachable on Android emulator: use `http://10.0.2.2:8000`. +- API unreachable on physical phone: use LAN IP, bind API to `0.0.0.0`, and allow Windows Firewall inbound traffic. +- Maps blank: verify Google Play services on Android and rebuild if native map config changed. +- Stripe Checkout returns to nowhere: verify `EXPO_PUBLIC_MOBILE_REDIRECT_BASE` and API payment redirect env values. +- Push warning about missing project id: run EAS setup and confirm `extra.eas.projectId` remains configured. -- **iOS**: The app uses the default Apple Maps renderer in `react-native-maps`. If you want - to use Google Maps tiles on iOS, add `ios.config.googleMapsApiKey` to `app.config.ts` - and rebuild the dev client. -- **Android**: Ensure Google Play services are available on your test device or emulator - for map tiles to load correctly. +More details: [docs/troubleshooting.md](../../docs/troubleshooting.md). diff --git a/apps/payment/.env.staging.example b/apps/payment/.env.staging.example index 4563ac0..12f356d 100644 --- a/apps/payment/.env.staging.example +++ b/apps/payment/.env.staging.example @@ -1,2 +1,10 @@ -STRIPE_API_KEY=sk_test_51MQGxRF3eKbuRVWZpEts6oT949m7gkFXG1E1KhRQnFTCkKkif2Lyf61D45OgtzR9qNJxpAsJjNHzSde0i7YeAfn100VyzHguLt -STRIPE_WEBHOOK_SECRET=sk_test_51MQGxRF3eKbuRVWZpEts6oT949m7gkFXG1E1KhRQnFTCkKkif2Lyf61D45OgtzR9qNJxpAsJjNHzSde0i7YeAfn100VyzHguLt \ No newline at end of file +ENVIRONMENT=staging +DATABASE_URL=sqlite:///./payment.db +STRIPE_API_KEY=sk_test_replace_me +STRIPE_WEBHOOK_SECRET=whsec_replace_me +TENANT_ID=public +PAYMENT_EVENT_TOPIC=payments +BOOKING_API_WEBHOOK_URL=http://api:8000/webhooks/payments +BOOKING_API_WEBHOOK_SECRET= +DEFAULT_CURRENCY=usd +PAYMENT_ALLOW_TEST_CLOCK=true diff --git a/apps/payment/README.md b/apps/payment/README.md index 6f2b4fe..42afa0c 100644 --- a/apps/payment/README.md +++ b/apps/payment/README.md @@ -1,91 +1,122 @@ # ShoeInn Payment Service -`apps/payment` is optional for most local development. +Optional FastAPI service that owns Stripe Checkout/PaymentIntent integration, payment records, Stripe webhook reconciliation, and payment-domain outbox rows. -You do not need this service for common backend/mobile flows such as provider appointment claiming and assignment, because the main API treats `PAYMENT_SERVICE_BASE_URL` as optional. +Most local development uses the main API in `PAYMENT_MODE=mock` and does not require this service. Start `apps/payment` only when validating real Stripe Checkout behavior. -Current behavior in the main API: +## Requirements -- local/demo API config should default to `PAYMENT_MODE=mock` -- if `PAYMENT_MODE=service`, `PAYMENT_SERVICE_BASE_URL` must be configured -- if `PAYMENT_MODE=service`, configure a reachable non-placeholder mobile/frontend redirect base with `PAYMENT_MOBILE_REDIRECT_BASE` -- the payment sync worker only starts in `service` mode when `PAYMENT_SERVICE_BASE_URL` is configured +- Python 3.11+ for local script workflow +- Python 3.12 base image for the Dockerfile +- Stripe test account and test keys +- Optional Stripe CLI for local webhook forwarding -## Current documentation status +## Architecture -This service is currently underdocumented compared to `apps/api` and `apps/mobile`. The startup notes below are the minimum known local workflow inferred from the checked-in code. +The mobile app never calls this service directly. The flow is: -## Local startup (Windows PowerShell) +1. Mobile confirms a booking through the API. +2. API calls `POST /payments/checkout-session` on the payment service. +3. Payment service creates or reuses a Stripe Customer and creates a Stripe Checkout Session. +4. Mobile opens the returned Stripe Checkout URL. +5. Stripe redirects to the configured API/browser return URL. +6. API/mobile can manually refresh payment status by booking id. +7. Stripe webhooks sent to the payment service update payment records and optionally call the booking API webhook. -```powershell -cd .\apps\payment -py -3.11 -m venv .venv -.\.venv\Scripts\Activate.ps1 -pip install -e . +## Local Startup + +Create `apps/payment/.env`: + +```env +STRIPE_API_KEY=sk_test_... +STRIPE_WEBHOOK_SECRET=whsec_... +DATABASE_URL=sqlite:///./payment.db +TENANT_ID=public +BOOKING_API_WEBHOOK_URL=http://localhost:8000/webhooks/payments +BOOKING_API_WEBHOOK_SECRET= +DEFAULT_CURRENCY=usd +PAYMENT_ALLOW_TEST_CLOCK=true ``` -Required environment variables from `app/config.py`: +Start with the helper script: ```powershell -$env:STRIPE_API_KEY="sk_test_..." -$env:STRIPE_WEBHOOK_SECRET="whsec_test_..." +.\scripts\start-payment.ps1 ``` -Optional local defaults: +Manual startup: ```powershell -$env:DATABASE_URL="sqlite:///./payment.db" -$env:TENANT_ID="public" +cd .\apps\payment +py -3.11 -m venv .venv +.\.venv\Scripts\Activate.ps1 +pip install -e . +python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8001 ``` -Start the service: +Health check: ```powershell -python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8001 +Invoke-RestMethod http://localhost:8001/health ``` -If you want the main API to talk to this service locally, set in `apps/api/.env`: +## Connect the API + +Set in `apps/api/.env`: ```env PAYMENT_MODE=service PAYMENT_SERVICE_BASE_URL=http://localhost:8001 PAYMENT_MOBILE_REDIRECT_BASE=shoeinn://app -# Development or standalone build: -# PAYMENT_MOBILE_REDIRECT_BASE=shoeinn://app -# Expo Go fallback: -# PAYMENT_MOBILE_REDIRECT_BASE=exp://:8081/-- ``` -If that redirect base is missing or still placeholder, booking confirmation will fail in `service` mode by design. - -This is the smallest supported real demo path. The mobile app opens hosted Stripe Checkout, Stripe returns directly to the configured mobile/frontend redirect base, and ShoeInn verifies payment state from the returned `booking_id` plus optional `session_id`. Manual "Check payment status" remains available as fallback. Refunds, disputes, and payouts remain deferred. +For Expo Go: -## Saved payment method behavior - -The payment service now creates or reuses a Stripe Customer when it has a customer email, and Checkout Sessions are created against that customer. This enables the closest Stripe-supported saved-card behavior for Checkout without building a full card-management UI. - -Limits to be aware of: +```env +PAYMENT_MOBILE_REDIRECT_BASE=exp://:8081/-- +``` -- Stripe Checkout `payment` mode does not let ShoeInn force an arbitrary "default card" selection. -- Checkout can prefill saved cards for a returning customer when those cards are eligible for redisplay. -- New payment methods can be saved for future reuse through Checkout when the customer opts in. +Then start the API: -## Local Stripe webhook forwarding +```powershell +.\scripts\start-api.ps1 -PaymentMode service -MobileRedirectBase "shoeinn://app" +``` -Manual payment refresh now reconciles the live Stripe Checkout Session even if webhook delivery is unavailable, but webhook forwarding is still the recommended local setup so successful payments update automatically. +## Stripe Webhooks -Use the Stripe CLI in a separate shell: +Forward Stripe events locally: ```powershell stripe listen --forward-to http://localhost:8001/payments/webhooks/stripe ``` -Copy the emitted signing secret into `STRIPE_WEBHOOK_SECRET`, then keep `BOOKING_API_WEBHOOK_URL` pointed at the API callback if you want the payment service to push updates automatically: +Copy the emitted `whsec_...` into `STRIPE_WEBHOOK_SECRET` and restart the payment service. + +Webhook handler: + +- `POST /payments/webhooks/stripe` + +Supported event handling includes Checkout completion/expiration, PaymentIntent success/failure, refunds, and disputes. + +## Testing ```powershell -$env:BOOKING_API_WEBHOOK_URL="http://localhost:8000/webhooks/payments" +cd .\apps\payment +.\.venv\Scripts\Activate.ps1 +python -m pytest tests -q ``` -## Expo return-flow note +Payment tests set in-memory SQLite and test Stripe env defaults in `tests/conftest.py`. + +## Current Limits + +- The payment service creates tables at startup with SQLAlchemy metadata; there is no Alembic migration track in `apps/payment`. +- Outbox rows are persisted, but no local broker publisher is implemented in this repository. +- Checkout can reuse Stripe Customers and eligible saved cards, but ShoeInn does not provide a separate card-management UI. +- Refund/dispute side effects are represented through payment state and compensating-action events; production support workflows remain future work. + +## More Documentation -Per Expo's linking guidance, a stable custom scheme requires a development build or standalone app. Expo Go can still be used for local return-flow testing, but `PAYMENT_MOBILE_REDIRECT_BASE` should be set explicitly to an `exp://.../--` URL instead of a custom scheme. +- [Payment architecture](../../docs/architecture/payment.md) +- [Environment reference](../../docs/environment.md) +- [Payment event notes](docs/consuming-payment-events.md) diff --git a/apps/payment/docs/consuming-payment-events.md b/apps/payment/docs/consuming-payment-events.md index 124e1bb..82b0f5b 100644 --- a/apps/payment/docs/consuming-payment-events.md +++ b/apps/payment/docs/consuming-payment-events.md @@ -1,8 +1,8 @@ # Consuming payment domain events -The payment service persists all outbound notifications in the `payment_events_outbox` table. A -background worker (or the shared outbox processor) should publish these rows to the platform event -bus using the topic provided by `PAYMENT_EVENT_TOPIC` (defaults to `payments`). +The payment service persists outbound payment-domain notifications in the `payment_events_outbox` table. The current repository does not include a broker publisher for this outbox; rows are durable records for future asynchronous publishing or support tooling. + +If a publisher is added later, `PAYMENT_EVENT_TOPIC` defaults to `payments` and should be used as the logical topic name. Each record contains: @@ -22,30 +22,21 @@ The payload schema is consistent across events: "status": "succeeded", "amount_expected": 5000, "amount_received": 5000, - "reason": "refund", // only for CompensatingActionRequested - "failure_code": "card_declined", // only for PaymentFailed - "dispute_id": "dp_123" // only for PaymentDisputed + "reason": "refund", + "failure_code": "card_declined", + "dispute_id": "dp_123" } ``` -## Authenticating payment events +## Current consumers -Downstream services should authenticate messages by verifying the signature applied by the outbox -publisher. When events are delivered over HTTP, use standard HMAC verification with a shared secret -managed by the platform IAM team. For consumers pulling events from the broker directly, attach an -API token issued by the tenancy service. Tokens are scoped to the tenant found in the payload and -must be validated before taking any action. +- Booking API: receives direct HTTP callbacks when `BOOKING_API_WEBHOOK_URL` is set. +- Manual reconciliation: the API payment sync worker and mobile "Check payment status" path can re-query status by booking id. -## Suggested consumers +## Future consumers -* **Booking service** – listens for `PaymentSucceeded` and `PaymentFailed` to confirm or release - reservations. When `CompensatingActionRequested` is emitted (after refunds or disputes), the - booking service should cancel any outstanding reservations tied to the booking ID. -* **Notifications** – sends customer emails/SMS when payments settle or fail. -* **Support tooling** – monitors `PaymentRefunded` and `PaymentDisputed` to surface action items for - customer support agents. Consumers should reconcile the dispute status with Stripe using the - `dispute_id` provided in the payload. +- Notifications: customer payment-settled and payment-failed delivery. +- Support tooling: refund and dispute queues. +- Event broker publisher: durable outbox publishing using `PAYMENT_EVENT_TOPIC`. -Services must store the most recent processed event ID to preserve idempotency, mirroring the -payment service's own `processed_stripe_events` table. This allows the platform to replay events -without creating duplicate side effects. +Future consumers should store the most recent processed event id to keep replay idempotent, mirroring the payment service's `processed_stripe_events` table. diff --git a/docs/architecture/api.md b/docs/architecture/api.md new file mode 100644 index 0000000..35950ea --- /dev/null +++ b/docs/architecture/api.md @@ -0,0 +1,66 @@ +# API Architecture + +The API lives in `apps/api` and is a FastAPI app with SQLAlchemy models and Alembic migrations. + +## Main Responsibilities + +- Authentication and JWT issuance +- User profile/address management +- Companies and company users +- Care categories and services +- Appointment holds, booking, and lifecycle +- Provider claiming and status updates +- Owner/company admin operational views +- Payment gateway orchestration +- Payment return endpoints +- Notification outbox and push token registration +- Live appointment events +- Demo seed data + +## Request Flow + +1. Mobile calls API over HTTP using bearer tokens. +2. Routers under `app/routers` validate and coordinate work. +3. SQLAlchemy models under `app/models` persist domain state. +4. Services under `app/services` handle pricing, payment gateway calls, notifications, and availability logic. +5. Notification and payment workers process asynchronous or polling work where enabled. + +## Database + +Local Compose runs PostgreSQL 15. API schema is managed by Alembic: + +```powershell +cd .\apps\api +python -m alembic upgrade head +``` + +Tests use in-memory SQLite through `tests/conftest.py`. + +## Payment Integration + +The API supports: + +- `PAYMENT_MODE=mock` for local/demo flows. +- `PAYMENT_MODE=service` for Stripe Checkout through `apps/payment`. + +In service mode, the API calls the payment service, stores appointment payment state, exposes return endpoints, and can run the payment sync worker. + +## Live Events + +Live events are process-local. Staging and demos should run one API instance until a shared transport is added. + +## Notification Architecture + +Notifications are queued in API tables. Delivery can be drained by: + +- In-process dispatcher when `ENABLE_NOTIFICATION_DISPATCHER=true`. +- Separate `notification-worker` service in staging Compose. + +## Demo Seed + +`POST /dev/seed?reset=true` seeds Shelby County. + +`POST /dev/seed?reset=true&demo_market=mt_juliet` seeds Mt. Juliet. + +The seed route is for local/staging demos and should be protected or disabled before public production exposure. + diff --git a/docs/architecture/deployment.md b/docs/architecture/deployment.md new file mode 100644 index 0000000..17da9a2 --- /dev/null +++ b/docs/architecture/deployment.md @@ -0,0 +1,78 @@ +# Deployment Architecture + +## Staging + +The current staging shape is defined by `apps/api/docker-compose.staging.yml`. + +Services: + +- `db` - PostgreSQL 15 on port `5432` +- `payment` - payment service on port `8001` +- `api` - FastAPI API on port `8000` +- `notification-worker` - drains notification work + +Start staging from `apps/api`: + +```powershell +Copy-Item .env.staging.example .env.staging +Copy-Item ..\payment\.env.staging.example ..\payment\.env.staging +docker compose -f .\docker-compose.staging.yml up --build -d +``` + +The API container runs: + +```bash +python -m alembic upgrade heads +python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 +``` + +Readiness: + +```powershell +Invoke-RestMethod http://localhost:8000/ready +``` + +Seed: + +```powershell +Invoke-RestMethod -Method Post "http://localhost:8000/dev/seed?reset=true&demo_market=mt_juliet" +``` + +## Pi Deployment + +For Raspberry Pi or small-host staging, use the staging Compose model: + +1. Install Docker and Compose plugin. +2. Copy repository to the host. +3. Create `apps/api/.env.staging` and `apps/payment/.env.staging`. +4. Use reachable DNS/HTTPS endpoints for mobile builds; do not point mobile at `localhost`. +5. Run `docker compose -f apps/api/docker-compose.staging.yml up --build -d`. +6. Run readiness checks and seed demo data. + +Live events are process-local, so keep one API instance on Pi/staging until shared fanout is implemented. + +## Mobile Preview Builds + +From `apps/mobile`: + +```bash +npx eas build --profile preview --platform android +npx eas build --profile preview --platform ios +``` + +Preview profile uses internal distribution. Configure API URL, maps key, redirect base, demo login flag, and demo market through `eas.json` or EAS environment settings. + +## Production + +Current production architecture is not fully separated from staging in code. The expected production direction is: + +- API container behind HTTPS ingress. +- PostgreSQL managed or separately backed up. +- Payment service behind private service networking where possible. +- Stripe webhooks delivered to payment service. +- Mobile production builds with demo logins disabled. +- Notification worker as a separate process. +- Seed routes protected or disabled at the edge. + +Before multi-instance production, replace process-local live events with a shared transport such as Redis pub/sub, Postgres LISTEN/NOTIFY, or a managed event bus. + diff --git a/docs/architecture/diagrams/flows.md b/docs/architecture/diagrams/flows.md new file mode 100644 index 0000000..67f7e6c --- /dev/null +++ b/docs/architecture/diagrams/flows.md @@ -0,0 +1,55 @@ +# Architecture Diagrams + +## System Context + +```mermaid +flowchart LR + Customer --> MobileApp + Provider --> MobileApp + Owner --> MobileApp + + MobileApp --> API + + API --> PostgreSQL + API --> PaymentService + + PaymentService --> Stripe + Stripe --> PaymentService +``` + +## Booking Lifecycle + +```mermaid +flowchart TD + CustomerBook --> AppointmentCreated + AppointmentCreated --> ProviderAssigned + ProviderAssigned --> EnRoute + EnRoute --> PickedUp + PickedUp --> Cleaning + Cleaning --> Ready + Ready --> OutForDelivery + OutForDelivery --> Delivered + Delivered --> Completed +``` + +## Payment Flow + +```mermaid +flowchart LR + Mobile --> API + API --> PaymentService + PaymentService --> StripeCheckout + StripeCheckout --> PaymentReturn + PaymentReturn --> AppointmentPaid +``` + +## Live Update Flow + +```mermaid +flowchart LR + ProviderStatusChange --> API + API --> LiveEvents + LiveEvents --> CustomerDevice + LiveEvents --> OwnerDashboard +``` + diff --git a/docs/architecture/mobile.md b/docs/architecture/mobile.md new file mode 100644 index 0000000..bb28f66 --- /dev/null +++ b/docs/architecture/mobile.md @@ -0,0 +1,59 @@ +# Mobile Architecture + +The mobile app lives in `apps/mobile` and is an Expo React Native app. + +## Core Areas + +- `src/navigation` - root tabs, auth gate, customer/company/admin stacks. +- `src/screens/home` - modern marketplace discovery, provider menu, service detail, booking flow, review/pay. +- `src/screens/customer` - legacy and customer-specific appointments, notifications, payment result, booking surfaces. +- `src/screens/provider` - provider dashboard and appointment detail. +- `src/screens/owner` - company owner/admin dashboard and appointment detail. +- `src/components/ui` - shared design primitives such as `AppScreen`, `Button`, `Card`, `StatusBadge`, `SectionHeader`, `BookingStepper`, loading/empty states, and media placeholders. +- `src/api` - HTTP client and service adapters. +- `src/auth` - demo login metadata. +- `src/hooks` - live events, push notifications, focused refresh, city/state helpers. +- `src/state` - Zustand stores for auth, booking, and company state. + +## Runtime Configuration + +Important variables: + +- `EXPO_PUBLIC_API_BASE_URL` +- `EXPO_PUBLIC_API_URL` +- `EXPO_PUBLIC_ENABLE_DEMO_LOGINS` +- `EXPO_PUBLIC_DEMO_MARKET` +- `EXPO_PUBLIC_GOOGLE_MAPS_API_KEY` +- `EXPO_PUBLIC_MOBILE_REDIRECT_BASE` + +See [environment.md](../environment.md). + +## Development + +```bash +cd apps/mobile +npm install +npm run typecheck +npm test -- --runInBand +npx expo start +``` + +For Android emulator, set API URL to `http://10.0.2.2:8000`. + +For a physical phone, use the computer LAN IP and make sure the API is reachable from the phone. + +## Builds + +`eas.json` contains `development`, `preview`, and `production` profiles. Preview Android builds use APK output and demo logins are enabled in the checked-in profile. + +```bash +npx eas build --profile preview --platform android +npx eas build --profile preview --platform ios +``` + +## UI Architecture Notes + +The current modern UI is tokenized through `src/theme/theme.ts` and shared primitives under `src/components/ui`. Prefer those components for new screens and fixes. + +Avoid fixed-width row layouts for narrow phones unless the content has an explicit wrap/stack fallback. + diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000..a966d38 --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,105 @@ +# ShoeInn Architecture Overview + +ShoeInn is a premium care marketplace demo composed of: + +- React Native Mobile App +- FastAPI Backend API +- Optional Payment Service +- PostgreSQL +- Stripe +- Live Event System +- Notification System + +## Responsibilities + +### Mobile App + +`apps/mobile` is an Expo React Native application. It provides customer discovery and booking, provider job workflows, company admin operations, notifications, maps, and payment return handling. It communicates with the backend API over HTTP and live update endpoints. + +### API + +`apps/api` is the core FastAPI backend. It owns users, auth, companies, care categories, services, appointment holds, appointments, assignment/claiming, status transitions, notification outbox, push token registration, payment gateway orchestration, and demo seed data. + +### Payment Service + +`apps/payment` is optional for most local work. In service payment mode, the API delegates Stripe Checkout creation and payment reconciliation to it. The service owns Stripe API calls, payment records, webhook handling, and payment-domain outbox rows. + +### PostgreSQL + +PostgreSQL stores API domain data in local/staging deployments. API migrations are managed by Alembic in `apps/api/alembic`. + +### Stripe + +Stripe is used only when the API is configured with `PAYMENT_MODE=service` and the payment service is running. Mock payment mode is the default local path. + +### Live Event System + +The current live update system is process-local and suitable for a single API instance. It supports customer/provider/owner appointment status updates in the demo flows. Multi-instance fanout needs a shared transport before horizontal API scaling. + +### Notification System + +The API stores notification work in `notification_outbox`. The notification worker drains queued delivery work. Mobile notification screens also consume notification API state and local archive/filter state. + +## System Context + +```mermaid +flowchart LR + Customer --> MobileApp + Provider --> MobileApp + Owner --> MobileApp + + MobileApp --> API + + API --> PostgreSQL + API --> PaymentService + + PaymentService --> Stripe + Stripe --> PaymentService +``` + +## Booking Lifecycle + +```mermaid +flowchart TD + CustomerBook --> AppointmentCreated + AppointmentCreated --> ProviderAssigned + ProviderAssigned --> EnRoute + EnRoute --> PickedUp + PickedUp --> Cleaning + Cleaning --> Ready + Ready --> OutForDelivery + OutForDelivery --> Delivered + Delivered --> Completed +``` + +## Payment Flow + +```mermaid +flowchart LR + Mobile --> API + API --> PaymentService + PaymentService --> StripeCheckout + StripeCheckout --> PaymentReturn + PaymentReturn --> AppointmentPaid +``` + +## Live Update Flow + +```mermaid +flowchart LR + ProviderStatusChange --> API + API --> LiveEvents + LiveEvents --> CustomerDevice + LiveEvents --> OwnerDashboard +``` + +## Documentation Map + +- [Mobile architecture](mobile.md) +- [API architecture](api.md) +- [Payment architecture](payment.md) +- [Deployment architecture](deployment.md) +- [Environment reference](../environment.md) +- [Getting started](../getting-started.md) +- [Troubleshooting](../troubleshooting.md) + diff --git a/docs/architecture/payment.md b/docs/architecture/payment.md new file mode 100644 index 0000000..907e504 --- /dev/null +++ b/docs/architecture/payment.md @@ -0,0 +1,63 @@ +# Payment Architecture + +`apps/payment` is an optional FastAPI service for Stripe-backed checkout. It is not required for mock local bookings. + +## Responsibilities + +- Create Stripe Checkout Sessions. +- Create/reuse Stripe Customers for returning customers. +- Persist payment records. +- Reconcile Stripe Checkout Session and PaymentIntent state. +- Handle Stripe webhooks. +- Optionally notify the booking API through `BOOKING_API_WEBHOOK_URL`. +- Persist payment-domain outbox rows. + +## Service Flow + +```mermaid +flowchart LR + Mobile --> API + API --> PaymentService + PaymentService --> StripeCheckout + StripeCheckout --> PaymentReturn + PaymentReturn --> API + API --> Mobile +``` + +## Local Defaults + +Payment service: + +- Port: `8001` +- Health: `GET /health` +- Database default: `sqlite:///./payment.db` + +API service mode: + +```env +PAYMENT_MODE=service +PAYMENT_SERVICE_BASE_URL=http://localhost:8001 +PAYMENT_MOBILE_REDIRECT_BASE=shoeinn://app +``` + +## Webhooks + +Stripe webhooks are handled at: + +```text +POST /payments/webhooks/stripe +``` + +Local forwarding: + +```powershell +stripe listen --forward-to http://localhost:8001/payments/webhooks/stripe +``` + +## Current Limits + +- No Alembic migrations for payment service schema. +- No local broker publisher for payment outbox rows. +- No separate card-management UI. +- Refund/dispute support exists at state/event level but production support workflows are future work. + diff --git a/docs/environment.md b/docs/environment.md new file mode 100644 index 0000000..1a9c69f --- /dev/null +++ b/docs/environment.md @@ -0,0 +1,67 @@ +# Environment Variable Reference + +Do not commit real secrets. Use `.env.example` and `.env.staging.example` files as templates. + +## API + +Read by `apps/api/app/core/config.py`. + +| Variable | Required | Default | Example | Description | +| --- | --- | --- | --- | --- | +| `DATABASE_URL` | Yes for Postgres | `sqlite:///./dev.db` | `postgresql+psycopg://postgres:postgres@localhost:5432/shoeinn` | API database connection. Use `localhost` for host-run API, `db` in Compose. | +| `API_HOST` | No | `0.0.0.0` | `0.0.0.0` | Host Uvicorn binds to in scripts/docs. | +| `API_PORT` | No | `8000` | `8000` | API port. | +| `JWT_SECRET` | Yes outside local | `changeme` | `replace-me` | JWT signing secret. Replace for staging/production. | +| `ALLOWED_ORIGINS` | No | `*` | `https://app.example.com` | Comma-separated CORS origins. | +| `ACCESS_TOKEN_TTL_MINUTES` | No | `15` | `15` | Access token lifetime. | +| `REFRESH_TOKEN_TTL_DAYS` | No | `30` | `30` | Refresh token lifetime. | +| `APPOINTMENT_HOLD_MINUTES` | No | `15` | `15` | Booking hold duration. | +| `HOLD_CLEANUP_INTERVAL_SECONDS` | No | `60` | `60` | Reserved for future scheduled hold cleanup. | +| `NOTIFICATION_DISPATCH_INTERVAL_SECONDS` | No | `5` | `5` | Notification dispatcher polling interval. | +| `NOTIFICATION_MAX_ATTEMPTS` | No | `5` | `5` | Max notification delivery attempts. | +| `NOTIFICATION_BACKOFF_SECONDS` | No | `30` | `30` | Retry backoff. | +| `ENABLE_NOTIFICATION_DISPATCHER` | No | `true` | `false` | In-process notification dispatcher toggle. Use `false` when a separate worker drains notifications. | +| `DB_AUTO_CREATE` | No | `false` | `false` | Auto-create DB schema outside migrations. Keep false for normal API runs. | +| `PAYMENT_MODE` | No | `mock` | `mock` or `service` | Mock mode for local demos, service mode for Stripe payment service. | +| `PAYMENT_SERVICE_BASE_URL` | Required for service mode | blank | `http://localhost:8001` | Payment service base URL. | +| `PAYMENT_CHECKOUT_SUCCESS_URL` / `PAYMENT_SUCCESS_URL` | Optional | blank | `https://api.example.com/payments/return/success` | Browser success return URL alias. | +| `PAYMENT_CHECKOUT_CANCEL_URL` / `PAYMENT_CANCEL_URL` | Optional | blank | `https://api.example.com/payments/return/cancel` | Browser cancel return URL alias. | +| `PAYMENT_MOBILE_REDIRECT_BASE` | Required for service mobile return path | blank | `shoeinn://app` | Mobile/frontend redirect base. Aliases: `PAYMENT_SUCCESS_URL_BASE`, `PAYMENT_RETURN_APP_URL`. | +| `PAYMENT_SERVICE_TIMEOUT_SECONDS` | No | `10.0` | `10` | Timeout for payment-service calls. | +| `PAYMENT_CURRENCY` | No | `usd` | `usd` | Default payment currency. | +| `ENABLE_PAYMENT_SYNC_WORKER` | No | `true` | `true` | Starts payment sync worker when service mode is configured. | +| `PAYMENT_SYNC_INTERVAL_SECONDS` | No | `5` | `5` | Payment sync polling interval. | + +## Mobile + +Read by `apps/mobile/app.config.ts`, `src/api/http.ts`, `src/api/services.ts`, demo login helpers, and map cards. + +| Variable | Required | Default | Example | Description | +| --- | --- | --- | --- | --- | +| `EXPO_PUBLIC_API_BASE_URL` | Recommended | auto-detected in some local paths | `http://192.168.1.14:8000` | Primary API base URL. | +| `EXPO_PUBLIC_API_URL` | Recommended compatibility | none | `http://192.168.1.14:8000` | Compatibility API URL used by some helpers and EAS config. | +| `EXPO_PUBLIC_APP_ENV` | No | none | `staging` | Build/environment label. | +| `EXPO_PUBLIC_ENABLE_DEMO_LOGINS` | No | `false` | `true` | Show demo login buttons. | +| `SHOW_DEMO_LOGINS` | No | `false` | `true` | Non-public fallback read by app config. | +| `EXPO_PUBLIC_DEMO_MARKET` | No | `shelby` | `mt_juliet` | Demo login/market selector. | +| `EXPO_PUBLIC_GOOGLE_MAPS_API_KEY` | Optional | none | `AIza...` | Enables Google Directions API route line, ETA, distance, and native map API keys. | +| `EXPO_PUBLIC_MOBILE_REDIRECT_BASE` | Required for service payment return | none | `shoeinn://app` | Mobile return base for Stripe Checkout. | +| `EXPO_PUBLIC_APP_URL` | Optional alias | none | `shoeinn://app` | Alias for mobile redirect base. | + +## Payment Service + +Read by `apps/payment/app/config.py`. + +| Variable | Required | Default | Example | Description | +| --- | --- | --- | --- | --- | +| `ENVIRONMENT` | No | `development` | `staging` | Payment service environment label. | +| `DATABASE_URL` | No | `sqlite:///./payment.db` | `sqlite:///./payment.db` | Payment service database. | +| `STRIPE_API_KEY` | Yes | none | `sk_test_...` | Stripe secret API key. | +| `STRIPE_WEBHOOK_SECRET` | Yes | none | `whsec_...` | Stripe webhook signing secret. | +| `TENANT_ID` | No | `public` | `public` | Tenant identifier stored with payments. | +| `PAYMENT_EVENT_TOPIC` | No | `payments` | `payments` | Logical outbox topic for future publishers. | +| `BOOKING_API_WEBHOOK_URL` | No | none | `http://localhost:8000/webhooks/payments` | Optional callback to booking API after payment state changes. | +| `BOOKING_API_WEBHOOK_SECRET` | No | none | `replace-me` | Optional callback header secret. | +| `DEFAULT_CURRENCY` | No | `usd` | `usd` | Payment default currency. | +| `PAYMENT_ALLOW_TEST_CLOCK` | No | `true` | `true` | Allows Stripe test clock behavior in non-production testing. | + diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..637945d --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,181 @@ +# Getting Started + +This guide takes a new developer from a clean checkout to a seeded API and running mobile app. + +## 1. Clone and Install Tools + +Required: + +- Git +- Python 3.11+ +- Node.js 20 LTS recommended +- npm +- Docker Desktop +- Expo Go for quick phone testing, or Android Studio/Xcode simulator tooling + +Optional: + +- EAS CLI for mobile builds: `npm install -g eas-cli` +- Stripe CLI for real payment-service webhook testing + +## 2. Start the API and Seed Demo Data + +Windows PowerShell from the repository root: + +```powershell +.\scripts\start-api.ps1 +``` + +This starts Postgres, installs API dependencies, runs migrations, starts the API on `http://localhost:8000`, and seeds the Shelby demo market. + +For Mt. Juliet: + +```powershell +.\scripts\start-api.ps1 -DemoMarket mt_juliet +``` + +Manual API flow: + +```powershell +cd .\apps\api +docker compose up -d +py -3.11 -m venv .venv +.\.venv\Scripts\Activate.ps1 +pip install -r ..\..\requirements.txt +pip install -e . +Copy-Item .env.example .env +python -m alembic upgrade head +python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +In another shell: + +```powershell +Invoke-RestMethod -Method Post "http://localhost:8000/dev/seed?reset=true" +``` + +## 3. Start the Mobile App + +Install and run: + +```powershell +cd .\apps\mobile +npm install +$env:EXPO_PUBLIC_API_BASE_URL="http://localhost:8000" +$env:EXPO_PUBLIC_API_URL=$env:EXPO_PUBLIC_API_BASE_URL +npx expo start +``` + +For Android emulator: + +```powershell +$env:EXPO_PUBLIC_API_BASE_URL="http://10.0.2.2:8000" +$env:EXPO_PUBLIC_API_URL=$env:EXPO_PUBLIC_API_BASE_URL +npx expo start +``` + +For a physical phone: + +```powershell +$env:EXPO_PUBLIC_API_BASE_URL="http://:8000" +$env:EXPO_PUBLIC_API_URL=$env:EXPO_PUBLIC_API_BASE_URL +npx expo start --tunnel +``` + +The helper script can do the same setup: + +```powershell +.\scripts\start-mobile.ps1 -ApiBaseUrl "http://:8000" -Tunnel +``` + +## 4. Demo Logins + +Shelby County demo, password `Password1!`: + +- Customer: `customer@shoeinn.com` +- Provider: `pelham.driver1@shoeinn.com` +- Company admin: `pelham.admin@shoeinn.com` +- Global admin: `admin@shoeinn.com` + +Mt. Juliet demo, password `Password123!`: + +- Customer: `customer.mtjuliet@shoeinn.demo` +- Provider: `provider.mtjuliet@shoeinn.demo` +- Company admin: `admin.mtjuliet@shoeinn.demo` + +To show demo login buttons: + +```powershell +$env:EXPO_PUBLIC_ENABLE_DEMO_LOGINS="true" +$env:EXPO_PUBLIC_DEMO_MARKET="mt_juliet" +``` + +## 5. Complete a Booking Flow + +1. Log in as a customer. +2. Browse companies/services. +3. Select a service. +4. Choose date and time. +5. Confirm booking. +6. In mock payment mode, the API completes the payment path without Stripe. +7. Open appointments and verify the new appointment appears. + +## 6. Provider and Owner Flow + +Provider: + +1. Log in as a provider. +2. Open dashboard. +3. Claim or open assigned jobs. +4. Move status through pickup, in progress, ready, delivery, and completed states. + +Company admin: + +1. Log in as company admin. +2. Open owner/company dashboard. +3. Review appointment queue and assignment state. +4. Assign or inspect provider/job status where available. + +## 7. Optional Real Stripe Checkout + +1. Create `apps/payment/.env` with Stripe test keys. +2. Start payment service: + +```powershell +.\scripts\start-payment.ps1 +``` + +3. Start API in service mode: + +```powershell +.\scripts\start-api.ps1 -PaymentMode service -MobileRedirectBase "shoeinn://app" +``` + +4. Start mobile with `EXPO_PUBLIC_MOBILE_REDIRECT_BASE=shoeinn://app` for a dev build, or an `exp://.../--` URL for Expo Go. + +## 8. Validate Before Making Changes + +API: + +```powershell +cd .\apps\api +.\.venv\Scripts\Activate.ps1 +python -m pytest tests -q +``` + +Mobile: + +```powershell +cd .\apps\mobile +npm run typecheck +npm test -- --runInBand +``` + +Payment: + +```powershell +cd .\apps\payment +.\.venv\Scripts\Activate.ps1 +python -m pytest tests -q +``` + diff --git a/docs/openspec.md b/docs/openspec.md new file mode 100644 index 0000000..1f7ea31 --- /dev/null +++ b/docs/openspec.md @@ -0,0 +1,38 @@ +# OpenSpec Workflow + +OpenSpec change artifacts live under `openspec/changes`. + +Common files in a change: + +- `proposal.md` +- `design.md` +- `tasks.md` +- `specs//spec.md` +- Optional QA notes or implementation notes + +Validate a change: + +```powershell +cmd /c openspec validate polish-mobile-modern-ui-experience --strict +``` + +Check change status: + +```powershell +cmd /c openspec status --change "polish-mobile-modern-ui-experience" --json +``` + +The current mobile polish work is tracked under: + +```text +openspec/changes/polish-mobile-modern-ui-experience +``` + +Use OpenSpec artifacts as implementation context, not as runtime configuration. Application startup, environment, deployment, and troubleshooting guidance now lives in: + +- [getting-started.md](getting-started.md) +- [environment.md](environment.md) +- [architecture/overview.md](architecture/overview.md) +- [staging.md](staging.md) +- [troubleshooting.md](troubleshooting.md) + diff --git a/docs/staging.md b/docs/staging.md index 078d2fd..dbd89ba 100644 --- a/docs/staging.md +++ b/docs/staging.md @@ -1,37 +1,45 @@ # Staging Runbook -This runbook defines the first supported staging slice for ShoeInn. +This runbook describes the current Docker Compose staging slice for ShoeInn. ## Scope -Staging v1 supports: +Staging supports: -- single FastAPI API instance - one PostgreSQL database +- one FastAPI API instance +- one payment service instance - one notification worker process -- mobile clients pointed at the staging API -- payment in declared **mock mode** +- mobile preview clients pointed at the staging API +- Stripe Checkout service payment mode when Stripe env values are configured -Staging v1 does **not** support multi-instance websocket fanout. Live events in the current code are process-local, so staging must remain single-instance until a shared transport is introduced. +Staging does not support multi-instance websocket fanout. Live events are process-local, so keep a single API instance until shared live-event transport is introduced. ## Files -- API env template: [apps/api/.env.staging.example](/C:/Users/aquin/source/repos/shoeinn/apps/api/.env.staging.example:1) -- Mobile env template: [apps/mobile/.env.staging.example](/C:/Users/aquin/source/repos/shoeinn/apps/mobile/.env.staging.example:1) -- Staging compose file: [apps/api/docker-compose.staging.yml](/C:/Users/aquin/source/repos/shoeinn/apps/api/docker-compose.staging.yml:1) +- API staging env template: `apps/api/.env.staging.example` +- Payment staging env template: `apps/payment/.env.staging.example` +- Mobile staging env template: `apps/mobile/.env.staging.example` +- Compose file: `apps/api/docker-compose.staging.yml` ## Startup -1. Create the API staging env file. +Create env files: ```powershell cd .\apps\api Copy-Item .env.staging.example .env.staging +Copy-Item ..\payment\.env.staging.example ..\payment\.env.staging ``` -2. Set a real staging JWT secret in `.env.staging`. +Edit both files: -3. Start staging services. +- Replace `JWT_SECRET`. +- Replace Stripe keys and webhook secret. +- Set payment return URLs to real staging URLs. +- Set `BOOKING_API_WEBHOOK_URL` appropriately for the Compose network or public staging host. + +Start: ```powershell docker compose -f .\docker-compose.staging.yml up --build -d @@ -40,109 +48,76 @@ docker compose -f .\docker-compose.staging.yml up --build -d This starts: - `db` +- `payment` - `api` - `notification-worker` -The `api` container applies `alembic upgrade heads` before starting Uvicorn. - -## Migration and readiness checks - -The staging API exposes: - -- `GET /health` for simple liveness -- `GET /ready` for dependency-aware readiness +The API container applies Alembic migrations before starting Uvicorn. -`/ready` validates: - -- database connectivity -- required `notification_outbox` table presence -- current Alembic head matches repo head -- current payment mode - -Check readiness: +## Readiness Checks ```powershell +Invoke-RestMethod http://localhost:8000/health Invoke-RestMethod http://localhost:8000/ready +Invoke-RestMethod http://localhost:8001/health +docker compose -f .\docker-compose.staging.yml ps ``` -Expected response shape: - -```json -{ - "status": "ready", - "database": "ok", - "migrations": "ok", - "notification_outbox": "ok", - "payment_mode": "mock", - "live_events_mode": "single_instance" -} -``` - -## Seed and reset +`/ready` validates database connectivity, migration head, notification table, payment mode, and live-event mode. -Staging v1 still uses the existing demo seed flow. +## Seed and Reset -Reset and reseed: +Shelby: ```powershell Invoke-RestMethod -Method Post "http://localhost:8000/dev/seed?reset=true" ``` -This is acceptable for staging demos and internal testing, but it should only be exposed to trusted operators. - -## Notification worker - -The notification worker is a first-class staging service in `docker-compose.staging.yml`. - -It is responsible for draining `notification_outbox` and delivering: - -- in-app notifications -- push notifications -- stubbed email/sms notifications - -Check that the worker is running: +Mt. Juliet: ```powershell -docker compose -f .\docker-compose.staging.yml ps +Invoke-RestMethod -Method Post "http://localhost:8000/dev/seed?reset=true&demo_market=mt_juliet" ``` -You should see `notification-worker` in a running state. +Seed endpoints are for trusted staging/demo operators only and should be protected before public exposure. -## Mobile staging configuration +## Mobile Staging Configuration -Create a staging env file or export a staging API URL before launching Expo or building a preview client: +Create or export: ```powershell cd .\apps\mobile Copy-Item .env.staging.example .env.staging ``` -The critical value is: +Critical values: ```env EXPO_PUBLIC_API_URL=https://api-staging.example.com +EXPO_PUBLIC_API_BASE_URL=https://api-staging.example.com +EXPO_PUBLIC_ENABLE_DEMO_LOGINS=true +EXPO_PUBLIC_MOBILE_REDIRECT_BASE=shoeinn://payment-return ``` Do not point staging mobile builds at localhost. -## Validation checklist - -Run these checks before calling staging usable: +## Validation Checklist 1. `docker compose -f apps/api/docker-compose.staging.yml up --build -d` succeeds. -2. `GET /health` returns `200`. -3. `GET /ready` returns `200` with `status=ready`. -4. Fresh DB migrations apply automatically on API startup. -5. `notification-worker` is running. -6. `POST /dev/seed?reset=true` succeeds. -7. Demo logins work for owner, provider, and customer. -8. Owner command center loads seeded jobs. -9. Provider can claim/update an appointment. -10. Customer can see status and notifications. -11. Live websocket behavior is verified on the single API instance. +2. API `/health` and `/ready` return success. +3. Payment `/health` returns success. +4. `notification-worker` is running. +5. Fresh DB migrations apply automatically on API startup. +6. Seed endpoint succeeds for the selected demo market. +7. Customer, provider, and company admin demo logins work. +8. Customer can browse, book, review/pay, and view appointment detail. +9. Provider can claim/update a job. +10. Company admin can view dashboard and job details. +11. Live updates are verified on the single API instance. +12. Stripe Checkout opens and payment status reconciles when service mode is enabled. ## Notes -- Payment remains intentionally simulated in staging v1 via `PAYMENT_MODE=mock`. If staging is later switched to `service`, `PAYMENT_SERVICE_BASE_URL` plus `PAYMENT_CHECKOUT_SUCCESS_URL` / `PAYMENT_CHECKOUT_CANCEL_URL` (or `PAYMENT_SUCCESS_URL` / `PAYMENT_CANCEL_URL`) must be set to real reachable return URLs. -- Live websocket fanout is **single-instance only** in staging v1. +- For Pi/small-host deployment, use the same Compose file and keep one API instance. - If staging later needs multiple API instances, live-event transport must move off in-memory process state. +- See [architecture/deployment.md](architecture/deployment.md) for broader deployment notes. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..2dfb446 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,129 @@ +# Troubleshooting + +## API and Database + +### `password authentication failed for user` + +The Postgres Docker volume was probably created with older credentials. + +```powershell +cd .\apps\api +docker compose down -v +docker compose up -d +python -m alembic upgrade head +``` + +### Alembic migration fails + +- Confirm `DATABASE_URL` points at the intended database. +- Use `localhost` when API/Alembic runs on the host. +- Use `db` only inside Docker Compose. +- Check `docker compose ps` and `docker compose logs db`. + +### `/ready` fails but `/health` passes + +`/health` only checks process liveness. `/ready` checks database, migrations, notification table, and payment mode. Inspect the JSON response and fix the failing dependency. + +## Mobile and Expo + +### Mobile cannot reach API + +- Android emulator: `http://10.0.2.2:8000`. +- iOS simulator or host browser: `http://localhost:8000`. +- Physical phone: `http://:8000`. +- Confirm API uses `--host 0.0.0.0`. +- Allow inbound Windows Firewall traffic for the API port. + +### Expo build or Metro failures + +```powershell +cd .\apps\mobile +npm install +npx expo start -c +``` + +If native config changed, rebuild the dev client instead of relying on Expo Go. + +### EAS device registration issues + +- Run `npx eas device:create`. +- Confirm the device is included in the internal distribution profile. +- Rebuild the `development` or `preview` profile after registering devices. + +### iOS certificate or provisioning issues + +- Run `npx eas credentials`. +- Regenerate credentials only if the existing profile/certificate is invalid. +- Make sure the bundle identifier remains `com.mrwrite.shoeinn`. + +## Maps + +### Map tiles blank + +- Android: use an emulator/device with Google Play services. +- iOS: rebuild after changing native Google Maps config. +- Confirm `EXPO_PUBLIC_GOOGLE_MAPS_API_KEY` is present before Expo starts. + +### Route, ETA, or distance missing + +Markers can render without Directions API. Route line/ETA/distance require `EXPO_PUBLIC_GOOGLE_MAPS_API_KEY` with Directions API enabled. + +## Payments + +### Stripe Checkout does not open + +- API must be in `PAYMENT_MODE=service`. +- `PAYMENT_SERVICE_BASE_URL` must point at a healthy payment service. +- `PAYMENT_MOBILE_REDIRECT_BASE` or `PAYMENT_RETURN_APP_URL` must be non-placeholder. + +### Payment service unavailable + +```powershell +Invoke-RestMethod http://localhost:8001/health +``` + +If it fails, check `apps/payment/.env` for `STRIPE_API_KEY` and `STRIPE_WEBHOOK_SECRET`, then restart: + +```powershell +.\scripts\start-payment.ps1 +``` + +### Stripe webhook not updating booking + +- Run `stripe listen --forward-to http://localhost:8001/payments/webhooks/stripe`. +- Copy the emitted `whsec_...` into `STRIPE_WEBHOOK_SECRET`. +- Set `BOOKING_API_WEBHOOK_URL=http://localhost:8000/webhooks/payments`. +- Use the appointment detail "Check payment status" action as a manual reconciliation fallback. + +## Demo Data + +### Stale or mixed demo records + +Reseed with reset: + +```powershell +Invoke-RestMethod -Method Post "http://localhost:8000/dev/seed?reset=true&demo_market=mt_juliet" +``` + +`reset=true` clears known demo markets before creating the selected market. + +## Live Updates and Notifications + +### WebSocket/live updates do not appear + +- Confirm the API is reachable from the mobile device. +- Confirm the logged-in role is customer, provider, company, or company admin. +- Staging currently supports single API instance live updates only. + +### Notifications do not send + +- Start the notification worker if validating queued delivery: + +```powershell +cd .\apps\api +.\.venv\Scripts\Activate.ps1 +python -m app.workers.notification_worker +``` + +- Check `notification_outbox.status`. +- Confirm mobile push tokens were registered through the app.