Payday Chat
A real-time, members-only network for online business founders.
The Problem
Founders needed a private, high-signal place to network, build teams, and scale revenue — not another noisy public feed. Payday Chat is invite-only and built around real-time collaboration.
My Role
Built solo with AI-assisted development — Flutter client, NestJS API, data model, real-time layer, and deployment.
Highlights
- Real-time messaging with presence and live events over WebSockets
- One NestJS API powering both a Flutter mobile app and a web client
- PostgreSQL domain model with Redis for caching and pub/sub fan-out
- NGINX gateway, Stripe payments, and OAuth authentication
Stack
Constraints
- Invite-only product — auth and access control had to be correct from day one.
- One backend, two clients (Flutter + web) — schema and contracts had to be shared, not duplicated.
- Realtime is a feature, not a nice-to-have — presence and live events must survive reconnects.
System Architecture
Key Trade-offs
The decisions worth defending — what I chose, what I turned down, and why.
Realtime transport
Chose
WebSockets with Redis pub/sub fan-out
Rejected
Long-polling or third-party realtime SaaS
Predictable latency, no per-message vendor cost, and Redis already in the stack for caching — one fewer moving part.
Mobile client framework
Chose
Flutter (single codebase for iOS + Android)
Rejected
Native Swift + Kotlin clients
Solo build — two native clients would have doubled the surface area and slowed iteration on the API.
API style
Chose
REST + dedicated WebSocket gateway
Rejected
GraphQL subscriptions
Simpler operational story, easier to cache at the NGINX layer, and the realtime channel stays an explicit, observable component.
What I'd Do Differently
An honest retrospective — the stuff I'd change with more time, more users, or a second pass.
- 1Introduce contract tests between the NestJS API and the Flutter client earlier — a few breakages were caught only at runtime.
- 2Move long-lived sockets to a dedicated process so API deploys don't drop client connections.
- 3Add structured event versioning from day one instead of retrofitting it once the schema started moving.
Technical Deep-Dive
Architecture, specifications, and implementation details.
Payday — Compliance & Legal Requirements
Version: 1.0
Scope: All features across the Payday platform
Applies to: Engineering, Product, and Operations teams
#1. Compliance Overview
Payday collects, processes, and stores personal data from business owners worldwide. The platform must comply with the following regulatory frameworks:
| Regulation | Scope | Risk Level |
|---|---|---|
| GDPR (EU) | Any user in the European Union or EEA | 🔴 High — fines up to €20M or 4% global revenue |
| CCPA (California) | Users resident in California, USA | 🟡 Medium — civil penalties up to $7,500/violation |
| COPPA (USA) | Under-13 users — must be fully prevented | 🔴 High — Payday must block minors entirely |
| PCI-DSS | Payment card processing | 🟡 Medium — Stripe handles card data; Payday must not touch it |
| CAN-SPAM (USA) | All marketing emails | 🟡 Medium |
| CASL (Canada) | Email marketing to Canadian users | 🟡 Medium |
| Apple App Store Guidelines | iOS app distribution | 🔴 High — rejection/removal risk |
| Google Play Policy | Android app distribution | 🔴 High — removal risk |
#2. GDPR Compliance
##2.1 Lawful Basis for Processing
| Data Category | Lawful Basis | Notes |
|---|---|---|
| Account registration data (email, name) | Contractual necessity | Required to provide the service |
| Profile data (niche, skills, revenue stage) | Contractual necessity | Core platform functionality |
| Payment data (Stripe customer ID) | Contractual necessity | Required for subscription billing |
| Email marketing | Consent | Explicit opt-in required at registration |
| Analytics / usage tracking | Legitimate interest | Must be disclosed in Privacy Policy |
| AI embeddings from profile text | Contractual necessity | Powers the match engine — must be disclosed |
##2.2 User Rights Implementation
| Right | API Endpoint | Implementation |
|---|---|---|
| Right to Access | GET /api/v1/members/me/data-export | Returns all personal data as JSON |
| Right to Rectification | PATCH /api/v1/members/me | User can update all profile fields |
| Right to Erasure | DELETE /api/v1/members/me/account | Anonymization flow — see section 2.3 |
| Right to Restriction | POST /api/v1/members/me/restrict-processing | Flags account, freezes AI processing |
| Right to Portability | GET /api/v1/members/me/data-export | Downloadable JSON of all user data |
| Right to Object | PATCH /api/v1/notifications/preferences | Opt out of specific processing types |
##2.3 Right to Erasure — Full Deletion Flow
User calls DELETE /api/v1/members/me/account
(or Admin calls DELETE /admin/members/:id/gdpr-erase)
Step 1: Validate request (re-authenticate with password or OAuth)
Step 2: Cancel active Stripe subscription immediately
Step 3: Anonymize PII in users table:
- email → deleted_{uuid}@deleted.payday
- phone → NULL
- password_hash → NULL
- refresh_tokens → []
- provider_id → NULL
Step 4: Anonymize member_profiles:
- display_name → "Deleted Member"
- avatar_url → NULL (delete file from storage)
- cover_url → NULL (delete file from storage)
- tagline → NULL
- bio → NULL
- location_city → NULL
- location_country → NULL
- social_links → {}
- embedding → NULL (remove AI embedding)
Step 5: Hard delete:
- All uploaded files (avatars, covers, attachments, message files)
- All direct messages (messages.deleted_at = NOW())
- All notification records
- All saved_posts records
- All refresh tokens and session data
Step 6: Soft-retain (community content — unlinked from identity):
- Posts remain (author_id points to anonymized user)
- Opportunity applications remain (anonymized)
- Reputation events remain (no PII — just points)
- Event attendance remains (no PII)
Step 7: Set users.status = 'deleted', users.deleted_at = NOW()
Step 8: Purge Redis: profile:{userId}, matches:{userId}, presence:{userId}
Step 9: Log deletion in moderation_logs (audit trail — no PII)
Step 10: Send deletion confirmation to original email address
Step 11: Completed within 30 days (GDPR requirement)
##2.4 Data Minimization
- Only collect data necessary for platform functionality
- Revenue stage collected as a range (not exact figure)
- Location: city + country only (no address, no precise coordinates)
- Phone number: optional, only for MFA
- No tracking pixels, no behavioral ad tracking
##2.5 Consent Management
Registration form must include:
☑ I agree to the Terms of Service (required — cannot register without)
☑ I agree to the Privacy Policy (required)
☐ I agree to receive marketing emails from Payday (optional — default unchecked)
Store in users table:
- marketing_consent: BOOLEAN DEFAULT FALSE
- marketing_consent_at: TIMESTAMPTZ
- terms_accepted_at: TIMESTAMPTZ
- privacy_policy_accepted_at: TIMESTAMPTZ
- terms_version: VARCHAR(20) -- e.g., "2025-01"
##2.6 Data Processing Records (Article 30 GDPR)
Maintain an internal record of all processing activities:
| Activity | Purpose | Data Categories | Retention |
|---|---|---|---|
| User registration | Provide service | Name, email, password hash | Until deletion |
| Profile data | Platform features | Business data, skills, niche | Until deletion |
| Messaging | Communication | Message content, read receipts | 2 years |
| AI embeddings | Match suggestions | Derived from profile text | Until profile update or deletion |
| Stripe billing | Payment processing | Subscription data, Stripe IDs | 7 years (tax/legal) |
| Email notifications | Service communication | Email address, notification content | 90 days |
| Moderation logs | Safety and compliance | Anonymized action logs | Indefinite |
##2.7 Data Breach Notification
If a data breach is detected:
- Contain the breach immediately (revoke tokens, isolate containers)
- Assess severity: which data was exposed, how many users affected
- If high risk to users: notify supervisory authority within 72 hours (GDPR Art. 33)
- If risk to individuals: notify affected users without undue delay (GDPR Art. 34)
- Document breach in incident log regardless of severity
#3. CCPA Compliance
##3.1 California User Rights
| Right | Implementation |
|---|---|
| Right to Know | Link to Privacy Policy + GET /members/me/data-export |
| Right to Delete | DELETE /members/me/account (same as GDPR erasure) |
| Right to Opt-Out of Sale | Payday does not sell personal data — state clearly in Privacy Policy |
| Right to Non-Discrimination | Same service regardless of privacy choices |
##3.2 "Do Not Sell My Personal Information"
Payday does not sell or share personal data with third parties for advertising. The Privacy Policy must explicitly state: "Payday does not sell, rent, or share your personal information with third parties for their marketing purposes."
The only third parties that receive personal data:
- Stripe — payment processing (contractual necessity)
- OpenAI — AI embedding generation (profile text only, no PII like email/phone)
- Email SMTP provider — email delivery (email address only)
#4. Age Verification & COPPA
Payday is a business platform for adults only. No users under 18 are permitted.
##Implementation Requirements
Registration form:
- Date of birth field OR
- Age confirmation checkbox: "I confirm I am 18 years of age or older"
- Store: users.date_of_birth OR users.age_verified: BOOLEAN
If user is under 18:
- Block registration immediately
- Do not store any of their data
- Show message: "Payday is for business owners aged 18 and over."
Terms of Service must state: "You must be at least 18 years old to use Payday."
##Apple App Store Requirement
Apple requires age rating. Payday should be rated 17+ due to:
- Unrestricted web access (business links)
- Financial transaction functionality (Stripe)
- User-generated content (posts, messages)
#5. Payment Compliance (PCI-DSS)
##5.1 Payday's Responsibility
Payday uses Stripe for all payment processing. This means:
- Payday never touches card data — Stripe's iframe handles all card entry
- Payday is SAQ-A compliant (lowest PCI-DSS scope) — no card data stored or transmitted
- Payday stores only:
stripe_customer_id,stripe_subscription_id, subscription status
##5.2 Stripe Integration Requirements
✅ Use Stripe Checkout (hosted page) — not custom card form
✅ Use Stripe Customer Portal for billing management
✅ Store only Stripe IDs — never card numbers, CVVs, or expiry dates
✅ Verify webhook signatures on every incoming webhook
✅ Use Stripe idempotency keys on all charge-creating API calls
✅ Enable Stripe Radar (fraud detection) in Dashboard
✅ Store Stripe secret key only in environment variable — never in code
##5.3 Subscription Billing Disclosures
Per Stripe, Apple, and legal requirements:
- Show recurring billing amount and interval clearly before checkout
- Show cancellation policy on pricing page
- Send receipt email after every successful charge (Stripe handles this)
- Send advance notice before annual renewal (7 days minimum)
#6. Email Compliance
##6.1 CAN-SPAM (USA)
All marketing emails must include:
- Physical mailing address of the business
- Clear "Unsubscribe" link that works within 10 business days
- Honest subject lines (no deceptive headers)
- Identify the email as an advertisement (if marketing)
Transactional emails (welcome, verify, receipts, notifications) are exempt from CAN-SPAM opt-out requirements.
##6.2 CASL (Canada)
For Canadian users:
- Require express consent before sending marketing emails
- Store consent timestamp and method
- Honor unsubscribe requests within 10 business days
##6.3 Email Unsubscribe Flow
User clicks unsubscribe link in email
→ GET /api/v1/notifications/unsubscribe?token={unsubscribeToken}&type={all|marketing|notifications}
→ Verify token (stored in users table or JWT)
→ Update notification_preferences.preferences accordingly
→ Show confirmation page: "You've been unsubscribed."
→ No re-subscription without explicit user action
#7. Apple App Store Compliance
##7.1 Required for App Store Approval
| Requirement | Implementation |
|---|---|
| Privacy Policy URL | Must be a live URL submitted during App Store Connect setup |
| Sign In with Apple | ✅ Already in spec — required if any social login is offered |
| In-App Purchase for digital goods | Subscriptions sold inside iOS app must use IAP (Apple takes 30%/15%) |
| App Privacy Labels | Complete "nutrition labels" in App Store Connect |
| Data Collection disclosure | List every data type collected (see section 7.2) |
| Age Rating | 17+ (see section 4) |
##7.2 App Privacy Labels (App Store Connect)
Must declare the following in App Store Connect → App Privacy:
| Data Type | Collected | Linked to User | Used for Tracking |
|---|---|---|---|
| Name | ✅ | ✅ | ❌ |
| Email Address | ✅ | ✅ | ❌ |
| Phone Number | ✅ optional | ✅ | ❌ |
| Photos or Videos | ✅ (profile photo) | ✅ | ❌ |
| User Content (posts, messages) | ✅ | ✅ | ❌ |
| Identifiers (User ID) | ✅ | ✅ | ❌ |
| Purchase History | ✅ | ✅ | ❌ |
| Other Financial Info | ❌ (Stripe handles) | — | — |
| Coarse Location (city) | ✅ optional | ✅ | ❌ |
| Browsing History | ❌ | — | — |
| Crash Data | ✅ (Sentry) | ❌ | ❌ |
##7.3 In-App Purchase Requirement
If Payday's iOS app allows users to purchase subscriptions inside the app, Apple requires using In-App Purchase (IAP):
- Apple takes 30% (first year) or 15% (subsequent years / small business program)
- To avoid IAP fees: do not allow subscription purchase inside the app — redirect to web browser
- Allowed: "Manage your subscription at paydayapp.com"
- Not allowed: "Subscribe here" button that opens a web checkout inside the app
Recommended approach: Redirect users to the web for subscription purchase. This avoids Apple's 30% cut and is fully compliant as long as you don't mention the price inside the app.
#8. Google Play Policy Compliance
##8.1 Required for Play Store Approval
| Requirement | Implementation |
|---|---|
| Privacy Policy URL | Submitted in Play Console — must be live |
| Prominent Disclosure | If collecting sensitive data, show disclosure before collection |
| Data Safety Section | Fill out in Play Console (similar to Apple privacy labels) |
| Permissions | Only request permissions needed (camera for photo upload, etc.) |
| Financial Features | Must disclose any in-app purchases |
##8.2 Data Safety Section (Play Console)
Must declare in Google Play Console → Data Safety:
- Data collected: Name, email, user content, photos, approximate location
- Data shared: Payment info shared with Stripe (payment processor)
- Security practices: Data encrypted in transit (TLS 1.3), at rest (AES-256)
- Data deletion: Users can request deletion via account settings
#9. Terms of Service — Required Clauses
The Terms of Service must include (have a lawyer review before launch):
1. Eligibility (must be 18+, must be a business owner)
2. Account registration and security responsibilities
3. Membership application and approval process
4. Membership tiers, pricing, and what's included
5. Subscription billing, auto-renewal, and cancellation policy
6. Refund policy (Payday's recommendation: no refunds for partial months)
7. Acceptable use policy — prohibited content and behavior
8. Intellectual property: users retain ownership of their content
9. License grant: users grant Payday license to display their content
10. Content moderation and account termination rights
11. Disclaimer of warranties
12. Limitation of liability
13. Indemnification
14. Governing law and dispute resolution
15. Changes to Terms — notification procedure
16. Contact information
#10. Privacy Policy — Required Sections
1. What data we collect and why
2. How we use your data (with lawful basis per GDPR)
3. Who we share data with (Stripe, OpenAI, email provider)
4. How long we retain data
5. Your rights (access, rectification, erasure, portability, objection)
6. How to exercise your rights (contact email + endpoint)
7. Cookies and tracking (what cookies are used, why)
8. Data transfers outside the EU (if applicable — Stripe, OpenAI are US-based)
9. Children's privacy (under 18 not permitted)
10. Changes to this policy
11. Contact details and Data Protection Officer (if required)
12. Supervisory authority right to lodge a complaint (EU users)
#11. Compliance Implementation Checklist
##Legal Documents
- Terms of Service drafted and reviewed by a lawyer
- Privacy Policy drafted (covers GDPR + CCPA + COPPA)
- Cookie Policy (if using cookies beyond session)
- Both documents live at public URLs before any user can register
##GDPR Technical Requirements
- Consent checkboxes implemented on registration form
- Consent timestamps stored in
userstable - Data export endpoint working (
GET /members/me/data-export) - Account deletion flow fully implemented and tested
- AI embedding disclosed in Privacy Policy
- Email unsubscribe flow working for all email types
- Data breach response procedure documented
##CCPA
- "Do Not Sell" statement in Privacy Policy
- Data export endpoint covers CCPA "Right to Know"
- Deletion flow covers CCPA "Right to Delete"
##Age Verification
- Age gate on registration (18+ confirmation)
- ToS states 18+ requirement
- Apple App Store rated 17+
##Payments
- Stripe Checkout used (no custom card form)
- Recurring billing clearly disclosed on pricing page
- Cancellation process explained
- Stripe webhook signature verification in code
- Apple IAP decision made before iOS app launch
- All marketing emails have unsubscribe link
- Physical business address in email footer
- Transactional vs marketing emails clearly separated in code
- Unsubscribe webhook working
##App Stores (when mobile app is built)
- Apple App Privacy Labels completed in App Store Connect
- Google Play Data Safety section completed
- Privacy Policy URL live and submitted to both stores
- Sign In with Apple implemented (required for iOS)
- App age rating set to 17+ (Apple) and appropriate rating (Google)