ONLINETIMER.TOOLS
Development documentation

CHRONOS Premium & Stripe — Developer Specification

Technical specification for CHRONOS Premium: architecture, Stripe webhook logic, subscription sync job, role system and system components.

1. Premium Architecture Overview

[User]
  ↓ clicks Upgrade
[Billing UI Modal]
  ↓ Stripe Checkout
[Stripe Checkout Session]
  ↓ payment success
[Stripe Webhook]
  → checkout.session.completed
  → customer.subscription.created
  → customer.subscription.updated
  → customer.subscription.deleted
  ↓
[Backend]
  → set user.role = premium
  → store subscription_id
  → store customer_id
  ↓
[App]
  → unlock premium features
  → remove ads
  → enable AI planners
  → enable unlimited presets

2. Stripe Webhook Specification

POST /functions/stripeWebhook

Events to handle

  • ▸checkout.session.completed
  • ▸customer.subscription.created
  • ▸customer.subscription.updated
  • ▸customer.subscription.deleted

Webhook logic (pseudocode)

verifyStripeSignature(request)

switch (event.type) {

  case "checkout.session.completed":
    userId = event.data.object.client_reference_id
    customerId = event.data.object.customer
    subscriptionId = event.data.object.subscription
    updateUser(userId, {
      stripe_customer_id: customerId,
      stripe_subscription_id: subscriptionId,
      role: "premium"
    })

  case "customer.subscription.updated":
    status = event.data.object.status
    user = findUserBySubscription(event.data.object.id)
    if (status == "active") user.role = "premium"
    else if (status == "past_due") user.role = "premium"   // grace period
    else if (status == "canceled") user.role = "free"
    save(user)

  case "customer.subscription.deleted":
    user = findUserBySubscription(event.data.object.id)
    user.role = "free"
    save(user)
}

Security requirements

  • ▸Validate the Stripe signature on every request
  • ▸Use the webhook secret, never a client-supplied value
  • ▸Never trust client-side role changes
  • ▸Store subscription and customer ids
  • ▸Log every webhook event

3. Subscription Sync Job

Scheduled task (cron): runs every 12 hours

for each user with stripe_subscription_id:
    subscription = stripe.subscriptions.retrieve(id)
    if subscription.status != "active":
        downgrade user.role to "free"

4. Premium Role System

  • ▸Roles: free, premium
  • ▸Stored on the user record
  • ▸Updated by the webhook and the sync job
  • ▸Frontend gating hides locked features

5. Premium Features (Technical)

  • ▸AI Study Planner
  • ▸AI Workout Planner
  • ▸AI ADHD Planner
  • ▸AI Deep Work Planner
  • ▸Unlimited presets & profiles
  • ▸No ads mode
  • ▸Premium widgets (branding + analytics)

6. System Components

Frontend

  • ▸Upgrade modal
  • ▸Premium badge
  • ▸Feature gating

Backend

  • ▸Stripe SDK
  • ▸Webhook handler
  • ▸Role manager
  • ▸Subscription sync

Database

  • ▸stripe_customer_id
  • ▸stripe_subscription_id
  • ▸role

7. Developer Notes

  • ▸Webhook must validate the Stripe signature
  • ▸Never trust client-side role changes
  • ▸Grace period recommended: 24–48h
  • ▸Test mode first, live mode later

8. Premium Feature Gating (Frontend)

Role model

user.role = "free" | "premium"    // anonymous visitors are "free"

Frontend logic

const { user } = useAuth();
const isPremium = user?.role === "premium";

// Premium-only feature
isPremium
  ? renderFeature()
  : renderLocked(() => openUpgradeModal())

Usage

  • ▸Every premium element carries a data-premium marker
  • ▸Locked elements stay visible and open the Upgrade Modal on click
  • ▸Unlocked elements behave normally
  • ▸Example: <button data-premium>AI Study Planner</button>

Behavior

  • ▸Free user: click opens the Upgrade Modal
  • ▸Premium user: the feature runs normally
  • ▸The role is never taken from the client — it comes from the user record