# PRD Modern Stack — Wavora / WAGW SaaS ## 1. Ringkasan **Wavora** adalah SaaS WhatsApp Gateway multi-tenant berbasis GOWA (`go-whatsapp-web-multidevice`) untuk mengelola nomor/device WhatsApp, kirim pesan via API, queue async, webhook, billing paket per nomor, dan dashboard admin. Versi ini adalah PRD modern hasil diskusi final: **tidak memakai PHP 7.4/MAMP sebagai runtime utama**. Source tetap boleh berada di folder htdocs agar rapi, tetapi aplikasi berjalan via container terpisah. ## 2. Keputusan Stack Final Recommended stack: ```text Framework : Laravel 11/12 Admin Panel : Custom `/app-admin` shell (Blade + BRAC) Database : PostgreSQL Queue : Redis Queue Worker : Laravel queue worker Scheduler : Laravel scheduler Storage : S3-compatible / MinIO local WA Engine : GOWA container Runtime Dev : OrbStack / Docker Compose on Mac mini Staging : Docker Compose on NUC Reverse Proxy : Caddy / Traefik / aaPanel proxy if needed ``` Alasan: - Laravel cepat untuk SaaS CRUD, auth, billing, queue, scheduler. - Custom admin shell mempercepat dashboard admin/platform dengan fleksibilitas penuh. - PostgreSQL kuat untuk multi-tenant, UUID, JSONB logs, reporting. - Redis Queue reliable dan mudah untuk async send. - Worker/scheduler Laravel sudah matang. - Docker/OrbStack memisahkan runtime dari MAMP PHP 7.4. ## 3. Non-Goals Versi Modern - Tidak memakai MAMP PHP 7.4 untuk menjalankan aplikasi Wavora. - Tidak memakai custom PHP manual router untuk versi modern. - Tidak langsung expose GOWA ke tenant. - Tidak menjanjikan official WhatsApp Business API compliance. - Tidak membuat spam/blast unlimited. - Tidak auto-paid untuk QRIS manual/transfer manual tanpa verifikasi. ## 4. Lokasi Project & Runtime Source code: ```text /Applications/MAMP/htdocs/other/fpm/wagw-saas ``` Runtime development: ```text OrbStack / Docker Compose ``` MAMP tetap untuk project lama: ```text MAMP Apache/PHP 7.4 -> legacy projects WAGW SaaS Docker -> Laravel PHP 8.3/8.4 + Postgres + Redis ``` Port dev contoh: ```text WAGW web : http://localhost:8088 PostgreSQL : localhost:54328 Redis : localhost:63798 MinIO optional : localhost:9000 / 9001 ``` Tailscale Serve mapping yang disarankan: ```text https://mac-mini-ardith.tail457f54.ts.net/ -> MAMP htdocs port 80 https://mac-mini-ardith.tail457f54.ts.net/wagw -> WAGW Docker web port 8088 https://mac-mini-ardith.tail457f54.ts.net/claw -> OpenClaw ``` ## 5. Deployment Strategy ### 5.1 Development Mac mini + OrbStack: ```text web/nginx or caddy app/php-fpm Laravel postgres redis worker scheduler minio optional gowa optional/local or remote NUC GOWA ``` ### 5.2 Staging NUC Docker Compose: ```text wagw-web wagw-app wagw-postgres wagw-redis wagw-worker wagw-scheduler wagw-minio optional gowa container or existing GOWA service ``` aaPanel can be used as: - domain/site manager. - reverse proxy to Docker Compose service. - not recommended as direct Laravel runtime for queue-heavy app. ### 5.3 Production Later Start with Docker Compose. Scale later to: - multiple API replicas. - multiple worker replicas. - multiple GOWA nodes/shards. - managed PostgreSQL/Redis. - object storage S3. ## 6. Product Scope MVP ### 6.1 Tenant Features - Register/login. - Tenant dashboard. - Add WhatsApp device/number. - QR login / pairing code. - Device status/reconnect/logout. - API key management. - Send message manual. - API send text/media. - Message logs and job status. - Webhook settings. - Package/subscription per WhatsApp number. - Invoice/payment page. - Upload payment proof for manual QRIS/transfer. ### 6.2 Platform Admin Features - Custom `/app-admin` dashboard. - Tenant management. - User/member management. - Device monitoring. - Plan/package CRUD. - Billing/invoice management. - Manual payment verification queue. - DOKU settings. - QRIS manual settings. - Bank transfer settings. - Queue/job monitor. - Usage/quota monitor. - Audit logs. ## 7. UI Template & Layout Direction The official visual direction for WAGW SaaS is defined in `docs/DESIGN_REFERENCE.md` (Buddy-inspired crisp digital blueprint). Use it as the single source of truth for colors, typography, spacing, radius, components, elevation, and layout. ### 7.1 Design System Adoption - Primary reference: `docs/DESIGN_REFERENCE.md`. - Tokens to implement in Tailwind CSS config: colors (Midnight Ink, Cloud White, Sour Apple, etc.), font families, spacing scale, radius values. - Blade components should encapsulate: PrimaryButton, GhostButton, Card, ElevatedCard, FeedbackCard, NavLink, TabPill, Input, Table, etc. - Use IBM Plex Sans (or Inter fallback) for all UI text; IBM Plex Mono (or Fira Code) strictly for technical/code snippets. ### 7.2 MVP UI Layers ```text Public/Landing : Custom Blade/Tailwind using design reference Tenant Dashboard : Custom Blade/Tailwind using design reference Platform Admin : Custom `/app-admin` Blade shell (BRAC-enforced) Docs/API : Clean documentation layout using design reference typography and colors ``` ### 7.3 Template References (for inspiration) These are references to accelerate implementation, not replacements for the design reference: - **Custom admin shell** for platform admin dashboard: tenant management, billing, device monitoring, queue/job monitor, payment verification, settings. - **TailAdmin** style for tenant dashboard: clean SaaS dashboard, stats cards, tables, forms, charts. - **Flowbite Admin Dashboard** as Tailwind component/layout reference. - **Tabler** as lightweight professional dashboard reference. - **Preline UI** for landing/public pages and reusable Tailwind sections. - **Cruip / Tailwind SaaS landing references** for marketing page inspiration. ### 7.4 Page Structure Public: - Landing page. - Pricing. - Docs/API overview. - Login/register. Tenant dashboard: - Overview. - WhatsApp devices. - Send message. - Message logs. - API keys. - Webhooks. - Billing/subscription. - Settings. Platform admin: - Tenants. - WA devices. - Plans/packages. - Invoices. - Payments/payment proofs. - Queue/job monitor. - Gateway nodes. - Audit logs. - Platform settings. ### 7.5 MVP UI Decision - Use **custom Blade/BRAC shell** for platform admin (brand with design reference tokens). - Implement **tenant dashboard** using custom Blade/Tailwind following the design reference. - Implement **public/landing** using custom Blade/Tailwind following the design reference. - Docs/API pages follow same design reference. - All custom UI must adhere to `docs/DESIGN_REFERENCE.md`. ## 8. Package Rules Per WhatsApp Number Setiap WhatsApp number/device wajib punya paket SaaS aktif. Rules: - Saat device baru ditambahkan → otomatis assign paket default **Free**. - Paket aktif berlaku **per WhatsApp number/device**, bukan hanya tenant global. - Tenant bisa punya beberapa nomor dengan paket berbeda. - Jika paket device expired/suspended/past_due → API send untuk device itu ditolak. - Admin bisa override limit per device. ## 9. Free Plan Free plan wajib tersedia dan tidak boleh dihapus. Default Free config: ```text price : Rp0 limit : 300 pesan/hari per WhatsApp number/device branding required : yes footer : — Sent via Wavora ``` Footer rules: - Berlaku untuk outgoing text. - Berlaku untuk caption media jika caption ada. - Tidak bisa dimatikan oleh tenant Free. - Footer ditambahkan sebelum job dikirim ke GOWA. - Payload original tetap disimpan untuk audit. ## 10. Paid Plans Contoh paket awal: | Paket | Device | Kuota | Branding | Target | |---|---:|---:|---|---| | Free | per device | 300/hari/device | wajib | testing/UMKM kecil | | Starter | per device | 5.000/bulan | off | bisnis kecil | | Growth | per device | 25.000/bulan | off | bisnis aktif | | Business | per device | 100.000/bulan | off | operasional besar | | Custom | custom | custom | custom | enterprise/dedicated | Plan fields: - name/code. - price monthly/yearly. - daily limit. - monthly limit. - branding required. - device/API/webhook feature limit. - feature flags. - active/inactive. ## 11. Async Send Architecture Default send message harus async/background. Flow: ```text Tenant hits API /send → Laravel validates API key, tenant, device, plan, quota, idempotency → DB transaction creates message row → dispatch Redis queue job → return 202 Accepted + message_id/job_id → queue worker sends to GOWA → update status sent/failed → webhook worker notifies tenant ``` API must not wait for GOWA send completion by default. Response default: ```http 202 Accepted ``` ```json { "success": true, "message_id": "uuid", "job_id": "uuid", "status": "queued", "message": "Message queued for delivery" } ``` Status endpoint: ```http GET /api/v1/messages/{message_id} GET /api/v1/messages/jobs/{job_id} ``` Optional sync mode: ```http X-Send-Mode: sync ``` Rules: - Sync mode only for admin/dev fallback. - Sync mode must be rate-limited and audited. - Production tenant API defaults async. ## 12. Queue Reliability Use Redis Queue via Laravel. Jobs: - `SendWhatsAppMessageJob` - `DeliverWebhookJob` - `CheckDeviceHealthJob` - `GenerateInvoiceJob` - `ExpirePaymentJob` - `ResetUsageCounterJob` - `CleanupOldLogsJob` Reliability rules: - retry with exponential backoff. - max attempts configurable. - failed jobs stored in failed jobs table. - job timeout set per message type. - idempotency key prevents duplicate sends. - stuck jobs recoverable. - queue worker supervised by container restart policy. - scheduler triggers periodic jobs. Recommended Laravel queue settings: ```text QUEUE_CONNECTION=redis queue:work redis --tries=3 --backoff=10,60,300 --timeout=120 ``` ## 13. Data Model — PostgreSQL Use UUID primary keys and JSONB for payload/logs. Core tables: ### Tenant/Auth - `tenants` - `users` - `tenant_members` - `roles` - `permissions` - `api_keys` ### WA Gateway - `wa_devices` - `device_plans` - `messages` - `message_jobs` - `message_attempts` - `message_logs` - `usage_counters` ### Webhooks - `webhook_endpoints` - `webhook_events` - `webhook_deliveries` ### Billing - `plans` - `subscriptions` - `invoices` - `payments` - `payment_proofs` - `payment_transactions` ### System - `gateway_nodes` - `audit_logs` - `settings` ## 14. Key Table Requirements ### `wa_devices` - `id uuid` - `tenant_id uuid` - `gowa_device_id text` - `label text` - `phone text nullable` - `status text` - `last_seen_at timestamptz nullable` - `created_at/updated_at` ### `device_plans` - `id uuid` - `tenant_id uuid` - `wa_device_id uuid` - `plan_id uuid` - `status active/past_due/suspended/expired/cancelled` - `started_at` - `expires_at nullable` - `daily_message_count integer` - `daily_reset_date date` - `monthly_message_count integer` - `monthly_reset_month text` ### `messages` - `id uuid` - `tenant_id uuid` - `wa_device_id uuid` - `recipient text` - `type text` - `status queued/processing/sent/failed/cancelled` - `original_payload jsonb` - `payload jsonb` - `branding_applied boolean` - `idempotency_key text nullable` - `gowa_message_id text nullable` - `queued_at/sent_at/failed_at` ### `message_attempts` - `id uuid` - `message_id uuid` - `attempt_no integer` - `status text` - `request_payload jsonb` - `response_payload jsonb nullable` - `error_message text nullable` - `duration_ms integer nullable` ## 15. Billing & Payment Supported methods: 1. DOKU Hosted Checkout. 2. QRIS Manual Helper. 3. Bank Transfer Manual. ### 15.1 DOKU Flow: ```text Invoice issued → tenant chooses DOKU → app creates hosted checkout → tenant pays → DOKU callback received → verify signature → mark payment verified → mark invoice paid → activate/renew device plan ``` Rules: - DOKU secrets stored outside repo. - callback idempotent. - verify amount and invoice reference. - raw callback stored in `payment_transactions`. ### 15.2 QRIS Manual Rules: - QRIS static payload stored by platform admin. - App can generate dynamic nominal QRIS if payload valid. - Tenant uploads payment proof. - Admin manually verifies. - No auto-paid. ### 15.3 Bank Transfer Manual Rules: - Admin sets bank name, account number, account owner, instructions. - Tenant uploads proof. - Admin approve/reject. - Reject requires reason. ### 15.4 Fee Calculation Invoice fields: ```text subtotal service_fee admin_fee grand_total ``` Rules: - Fee can be flat or percent. - Fee can be global or per tenant override. - Fee calculated and frozen when invoice issued. - Historical invoices never change after settings changed. ## 16. GOWA Integration GOWA remains WA engine. Wavora wraps GOWA endpoints: - device login/status/reconnect/logout. - send text/media/contact/location/poll. - message revoke/reaction/read/download. - chat list/message history optional. Tenant never sees GOWA Basic Auth. Device scoping: ```http X-Device-Id: ``` Wavora maps internal `wa_devices.id` to GOWA `device_id`. ## 17. API Requirements Tenant auth: ```http Authorization: Bearer Idempotency-Key: ``` Core endpoints: ```http GET /api/v1/devices POST /api/v1/devices GET /api/v1/devices/{id} POST /api/v1/devices/{id}/login-qr POST /api/v1/devices/{id}/login-code POST /api/v1/devices/{id}/reconnect POST /api/v1/devices/{id}/logout POST /api/v1/messages/text POST /api/v1/messages/image POST /api/v1/messages/file POST /api/v1/messages/video POST /api/v1/messages/audio GET /api/v1/messages/{id} GET /api/v1/messages/jobs/{job_id} GET /api/v1/billing/plans GET /api/v1/billing/invoices GET /api/v1/billing/invoices/{id} POST /api/v1/billing/invoices/{id}/pay/doku POST /api/v1/billing/invoices/{id}/pay/qris-manual POST /api/v1/billing/invoices/{id}/pay/bank-transfer POST /api/v1/billing/payments/{id}/proof ``` Webhooks: ```http POST /webhooks/doku POST /api/v1/webhooks/test ``` ## 18. Security - API keys hashed at rest. - Secrets only in `.env`, never committed. - Tenant isolation in every query/service. - Use Laravel policies/gates. - Custom admin shell protected by BRAC (role/permission via `BracService`). - Rate limit by tenant/API key/device/recipient. - Validate uploads. - Sanitize message logs. - Audit sensitive actions. - DOKU callback signature validation. - Webhook HMAC signature for tenant callbacks. ## 19. Observability MVP: - Laravel logs. - queue failed jobs. - message attempt logs. - dashboard queue size. - device status monitor. - daily usage counter. Later: - Grafana. - Prometheus. - Loki. - Sentry. - Uptime Kuma. ## 20. Docker Compose Services Target services: ```text wagw-web wagw-app wagw-postgres wagw-redis wagw-worker wagw-scheduler wagw-minio optional wagw-gowa optional/local ``` Dev command examples: ```bash docker compose up -d php artisan migrate php artisan queue:work redis php artisan schedule:work ``` In container, worker/scheduler should run as separate services. ## 21. Development Workflow - Use OrbStack on Mac mini. - Do not rely on MAMP PHP version. - Keep source in `wagw-saas` folder. - Use opencode for implementation tasks. - Before coding: read this PRD + HINDSIGHT.md + recall Hindsight. - After meaningful changes: retain summary to Hindsight. - Run tests/lint before report. Recommended first build order: 1. Laravel project scaffold. 2. Docker Compose PHP/Postgres/Redis. 3. Custom admin shell (`/app-admin`) setup. 4. Tenant/auth/API key foundation. 5. Plan/device plan model. 6. GOWA client service. 7. Async message queue. 8. Free plan footer + 300/day/device enforcement. 9. Billing/invoice/payment. 10. Webhook delivery. ## 22. Acceptance Criteria MVP - App runs via OrbStack/Docker, independent from MAMP PHP 7.4. - Laravel app connects to PostgreSQL and Redis. - Custom `/app-admin` accessible. - Tenant can add WA device; default Free plan assigned. - Free plan enforces 300 messages/day/device. - Free plan applies mandatory footer to text/caption. - Paid plan can be activated per device. - API send returns `202 Accepted` quickly. - Worker sends queued message to GOWA. - Message status can be checked by API. - Retry/backoff works on failure. - Webhook delivery queued and signed. - DOKU checkout flow defined and callback idempotent. - QRIS manual/transfer manual require admin verification. - All sensitive actions audited. ## 23. Open Questions - Laravel 11 or 12 final? Prefer latest stable supported by selected packages. - Custom admin shell version final? - Use existing NUC GOWA for dev, or local GOWA container in OrbStack? - Payment DOKU implemented in MVP or after manual payment MVP? - Branding footer exact text. - Domain for staging: `wagw.digitechnesia.my.id`? - Use Caddy or aaPanel reverse proxy for staging?