Skip to content
Return to Projects
Case Study

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

FlutterDartNestJSPostgreSQLRedisNGINXStripe

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

Clients
Flutter Mobile App
Web App
Gateway
NGINX
API
NestJS REST
WebSocket Gateway
Data
PostgreSQL
Redis (cache + pub/sub)
External
Stripe
OAuth

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.

  1. 1Introduce contract tests between the NestJS API and the Flutter client earlier — a few breakages were caught only at runtime.
  2. 2Move long-lived sockets to a dedicated process so API deploys don't drop client connections.
  3. 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:

RegulationScopeRisk 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-DSSPayment 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 GuidelinesiOS app distribution🔴 High — rejection/removal risk
Google Play PolicyAndroid app distribution🔴 High — removal risk

#2. GDPR Compliance

##2.1 Lawful Basis for Processing

Data CategoryLawful BasisNotes
Account registration data (email, name)Contractual necessityRequired to provide the service
Profile data (niche, skills, revenue stage)Contractual necessityCore platform functionality
Payment data (Stripe customer ID)Contractual necessityRequired for subscription billing
Email marketingConsentExplicit opt-in required at registration
Analytics / usage trackingLegitimate interestMust be disclosed in Privacy Policy
AI embeddings from profile textContractual necessityPowers the match engine — must be disclosed

##2.2 User Rights Implementation

RightAPI EndpointImplementation
Right to AccessGET /api/v1/members/me/data-exportReturns all personal data as JSON
Right to RectificationPATCH /api/v1/members/meUser can update all profile fields
Right to ErasureDELETE /api/v1/members/me/accountAnonymization flow — see section 2.3
Right to RestrictionPOST /api/v1/members/me/restrict-processingFlags account, freezes AI processing
Right to PortabilityGET /api/v1/members/me/data-exportDownloadable JSON of all user data
Right to ObjectPATCH /api/v1/notifications/preferencesOpt 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:

ActivityPurposeData CategoriesRetention
User registrationProvide serviceName, email, password hashUntil deletion
Profile dataPlatform featuresBusiness data, skills, nicheUntil deletion
MessagingCommunicationMessage content, read receipts2 years
AI embeddingsMatch suggestionsDerived from profile textUntil profile update or deletion
Stripe billingPayment processingSubscription data, Stripe IDs7 years (tax/legal)
Email notificationsService communicationEmail address, notification content90 days
Moderation logsSafety and complianceAnonymized action logsIndefinite

##2.7 Data Breach Notification

If a data breach is detected:

  1. Contain the breach immediately (revoke tokens, isolate containers)
  2. Assess severity: which data was exposed, how many users affected
  3. If high risk to users: notify supervisory authority within 72 hours (GDPR Art. 33)
  4. If risk to individuals: notify affected users without undue delay (GDPR Art. 34)
  5. Document breach in incident log regardless of severity

#3. CCPA Compliance

##3.1 California User Rights

RightImplementation
Right to KnowLink to Privacy Policy + GET /members/me/data-export
Right to DeleteDELETE /members/me/account (same as GDPR erasure)
Right to Opt-Out of SalePayday does not sell personal data — state clearly in Privacy Policy
Right to Non-DiscriminationSame 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

RequirementImplementation
Privacy Policy URLMust 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 goodsSubscriptions sold inside iOS app must use IAP (Apple takes 30%/15%)
App Privacy LabelsComplete "nutrition labels" in App Store Connect
Data Collection disclosureList every data type collected (see section 7.2)
Age Rating17+ (see section 4)

##7.2 App Privacy Labels (App Store Connect)

Must declare the following in App Store Connect → App Privacy:

Data TypeCollectedLinked to UserUsed 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

RequirementImplementation
Privacy Policy URLSubmitted in Play Console — must be live
Prominent DisclosureIf collecting sensitive data, show disclosure before collection
Data Safety SectionFill out in Play Console (similar to Apple privacy labels)
PermissionsOnly request permissions needed (camera for photo upload, etc.)
Financial FeaturesMust 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 users table
  • 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

##Email

  • 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)
~ End of Document ~