Skip to main content

Payment System (Subscription Management)

Status: ✅ Complete (Mock Implementation) - Production payment provider (Stripe) deferred until after testing
Last Updated: 2026-01-26


Overview​

The payment system handles subscription management for ARC // OS. It uses an adapter pattern (similar to email service and file storage) to allow easy switching between payment providers.

Design Principle: Interface-based design allows swapping payment providers (mock → Stripe → future providers) without code changes.

Philosophy​

  • Free Mode: Full access to core features—no limits, no data selling
  • Subscribed Mode: Enhanced features + support ongoing development
  • Privacy Promise: We never sell your data, pictures, or personal information—regardless of subscription status
  • Transparent: Subscription helps maintain the platform, not monetize user data

Subscription Tiers​

Free Mode (Default):

  • ✅ All core features (daily planning, tasks, training, nutrition, sleep, journaling, etc.)
  • ✅ Core gamification (XP, streaks, basic achievements, daily battles, shop)
    • Note: Core gamification remains free as it's an important driver of daily usage
  • ✅ Social features (friends, messaging)
  • ✅ Basic integrations (Google Fit - if implemented)
  • ✅ All data is yours—no limits
  • ✅ No data selling—ever

Subscribed Mode:

  • ✅ Everything in Free Mode, plus:
  • ✅ Enhanced gamification features:
    • Advanced battle features (weekly boss prep insights, advanced battle analytics)
    • Premium skill tree nodes
    • Exclusive gear sets and items
    • Enhanced idle progression bonuses
    • Advanced collection features
  • ✅ Developer/Integration features:
    • Outgoing webhooks (trigger events to external services)
    • Advanced integrations (Strava, Zwift, and other niche products)
    • Custom API access (if needed for advanced use cases)
    • Integration management UI
  • ✅ Advanced features:
    • Advanced analytics and insights
    • Priority support
    • Early access to new features
    • Enhanced customization options
    • Additional storage for files and photos
    • Advanced export/import features

Design Principles​

  • No aggressive gating - Core features remain free
  • Gamification balance - Core gamification (XP, streaks, daily battles) stays free as it drives daily usage; enhanced features can be subscription-only
  • Value-driven - Subscribed features provide clear value (webhooks, integrations, advanced analytics)
  • Transparent - Clear what's free vs subscribed
  • Privacy-first - Subscription doesn't change privacy promise
  • User-friendly - Easy to upgrade, easy to cancel
  • Integration strategy - Basic integrations (Google Fit) free; niche products (Strava, Zwift) subscription-only

Architecture​

Adapter Pattern​

IPaymentAdapter (interface)
├── MockPaymentAdapter (development/testing)
└── StripePaymentAdapter (production - future)

Key Benefits:

  • Easy testing with mock adapter
  • Simple provider switching via environment variable
  • Consistent interface across all providers
  • No code changes needed when switching providers

Interface Design​

IPaymentAdapter Interface​

/**
* Payment adapter interface
* Allows easy swapping between mock, Stripe, and future payment providers
*/
export interface IPaymentAdapter {
/**
* Create a subscription checkout session
* @param userId - User ID
* @param planId - Subscription plan ID
* @param successUrl - URL to redirect after successful payment
* @param cancelUrl - URL to redirect after cancelled payment
* @returns Checkout session URL
*/
createCheckoutSession(
userId: string,
planId: string,
successUrl: string,
cancelUrl: string
): Promise<string>;

/**
* Create a customer portal session
* @param userId - User ID
* @param returnUrl - URL to return to after portal session
* @returns Portal session URL
*/
createPortalSession(userId: string, returnUrl: string): Promise<string>;

/**
* Handle webhook event from payment provider
* @param payload - Raw webhook payload
* @param signature - Webhook signature for verification
* @returns Processed webhook event
*/
handleWebhook(payload: string, signature: string): Promise<WebhookEvent>;

/**
* Get subscription details
* @param subscriptionId - Subscription ID from payment provider
* @returns Subscription details
*/
getSubscription(subscriptionId: string): Promise<SubscriptionDetails>;

/**
* Cancel a subscription
* @param subscriptionId - Subscription ID from payment provider
* @returns Cancelled subscription details
*/
cancelSubscription(subscriptionId: string): Promise<SubscriptionDetails>;

/**
* Update subscription (change plan, etc.)
* @param subscriptionId - Subscription ID from payment provider
* @param updates - Subscription updates
* @returns Updated subscription details
*/
updateSubscription(
subscriptionId: string,
updates: SubscriptionUpdates
): Promise<SubscriptionDetails>;
}

Types​

/**
* Webhook event types
*/
export type WebhookEventType =
| 'checkout.session.completed'
| 'customer.subscription.created'
| 'customer.subscription.updated'
| 'customer.subscription.deleted'
| 'invoice.payment_succeeded'
| 'invoice.payment_failed';

/**
* Webhook event
*/
export interface WebhookEvent {
type: WebhookEventType;
subscriptionId?: string;
userId: string;
metadata: Record<string, unknown>;
timestamp: Date;
}

/**
* Subscription details
*/
export interface SubscriptionDetails {
id: string;
userId: string;
status: 'active' | 'canceled' | 'past_due' | 'unpaid' | 'trialing';
planId: string;
currentPeriodStart: Date;
currentPeriodEnd: Date;
cancelAtPeriodEnd: boolean;
metadata: Record<string, unknown>;
}

/**
* Subscription updates
*/
export interface SubscriptionUpdates {
planId?: string;
cancelAtPeriodEnd?: boolean;
metadata?: Record<string, unknown>;
}

Implementations​

MockPaymentAdapter (Development/Testing)​

Purpose: Mock implementation for development and testing.

Features:

  • Simulates payment flow without real payments
  • Stores subscription data in memory/database
  • Provides test endpoints for webhook simulation
  • No external API calls

Usage:

// Automatically used when PAYMENT_PROVIDER=mock
const adapter = getPaymentAdapter();
const checkoutUrl = await adapter.createCheckoutSession(
userId,
'subscribed',
'https://app.arcos.com/success',
'https://app.arcos.com/cancel'
);

Test Endpoints:

  • POST /api/payments/test/checkout - Simulate successful checkout
  • POST /api/payments/test/webhook - Simulate webhook events
  • GET /api/payments/test/subscriptions - View mock subscriptions

StripePaymentAdapter (Production - Future)​

Purpose: Production Stripe integration.

Features:

  • Real payment processing via Stripe
  • Secure webhook verification
  • Customer portal integration
  • Subscription management

Setup:

  1. Create Stripe account
  2. Get API keys (publishable and secret)
  3. Configure webhook endpoint
  4. Set environment variables

Environment Variables:

PAYMENT_PROVIDER=stripe
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

Service Layer​

PaymentService​

Purpose: Business logic layer for payment operations.

Methods:

  • createSubscription(userId, planId) - Create subscription checkout
  • handleWebhookEvent(event) - Process webhook events
  • getUserSubscription(userId) - Get user's current subscription
  • cancelSubscription(userId) - Cancel user's subscription
  • updateSubscription(userId, updates) - Update subscription
  • verifySubscriptionAccess(userId, feature) - Check feature access

Integration:

  • Uses IPaymentAdapter for payment provider operations
  • Manages Subscription database records
  • Handles subscription state transitions
  • Enforces business rules (grace periods, etc.)

Database Schema​

model Subscription {
id String @id @default(uuid())
userId String
provider String // 'stripe', 'mock', etc.
providerSubscriptionId String // Subscription ID from payment provider
planId String // 'free', 'subscribed'
status String // 'active', 'canceled', 'past_due', 'unpaid', 'trialing'
currentPeriodStart DateTime
currentPeriodEnd DateTime
cancelAtPeriodEnd Boolean @default(false)
canceledAt DateTime?
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@unique([userId]) // One subscription per user
@@index([userId])
@@index([providerSubscriptionId])
@@index([status])
@@map("subscriptions")
}

API Endpoints​

Subscription Management​

  • GET /api/subscription - Get current subscription
  • POST /api/subscription/checkout - Create checkout session
  • POST /api/subscription/cancel - Cancel subscription
  • POST /api/subscription/update - Update subscription
  • GET /api/subscription/portal - Get customer portal URL

Webhooks​

  • POST /api/subscription/webhook - Payment provider webhook endpoint

Testing (Mock Only)​

  • POST /api/payments/test/checkout - Simulate successful checkout
  • POST /api/payments/test/webhook - Simulate webhook events
  • GET /api/payments/test/subscriptions - View mock subscriptions

Implementation Status​

Phase 1: Mock Adapter (Development) ✅ Complete​

  • ✅ Create IPaymentAdapter interface - Complete: Created payment-adapter.interface.ts with full interface definition
  • ✅ Implement MockPaymentAdapter - Complete: Full implementation with mock checkout, cancellation, webhook handling, and invoice generation
  • ✅ Create SubscriptionService with business logic - Complete: Full service with CRUD operations, feature gating, and subscription management
  • ✅ Database schema: Subscription table - Complete: Added Subscription model to Prisma schema with all required fields
  • ✅ API routes for subscription management - Complete: Full API routes (GET /subscription, POST /subscription/upgrade, POST /subscription/cancel, GET /subscription/features, POST /subscription/webhook, POST /subscription/admin/grant)
  • ✅ Admin endpoint for testing - Complete: POST /subscription/admin/grant endpoint for admin users to manually grant subscriptions
  • ✅ Comprehensive test coverage - Complete: 28 service tests, route tests, and component tests all passing

Phase 2: Stripe Integration (Production) ⏳ Deferred​

  • ⏳ Implement StripePaymentAdapter - Deferred until after testing
  • ⏳ Stripe webhook verification - Deferred until after testing
  • ⏳ Customer portal integration - Deferred until after testing
  • ⏳ Subscription plan configuration - Deferred until after testing
  • ⏳ Production testing - Deferred until after testing
  • ⏳ Migration from mock to Stripe - Deferred until after testing

Phase 3: Frontend Integration ✅ Complete​

  • ✅ Subscription status display - Complete: Added subscription section to SettingsPage with plan, status, and period information
  • ✅ Checkout flow UI - Complete: Upgrade button triggers checkout flow, handles success/cancel pages
  • ✅ Subscription management UI - Complete: Full SubscriptionPage with current tier display, upgrade button, cancel subscription flow, and mock payment method display
  • ✅ Feature gating UI - Complete: Implemented reusable components (LockedFeatureBadge, LockedFeaturePrompt, UpgradeCTA) for feature gating with graceful degradation
  • ✅ Feature comparison page - Complete: Integrated into SubscriptionPage with feature list showing available/locked features
  • ✅ Comprehensive tests - Complete: Added comprehensive tests for useSubscription hook (5 tests) and SubscriptionPage component (8 tests), all passing

Environment Variables​

# Payment Provider
PAYMENT_PROVIDER=mock # 'mock' or 'stripe'

# Stripe (when PAYMENT_PROVIDER=stripe)
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Subscription Plans
SUBSCRIPTION_PLAN_ID=price_... # Stripe price ID

Testing​

Mock Adapter Testing​

describe('PaymentService with MockAdapter', () => {
it('should create checkout session', async () => {
const checkoutUrl = await paymentService.createSubscription(
userId,
'subscribed'
);
expect(checkoutUrl).toContain('/checkout');
});

it('should handle webhook events', async () => {
const event = await paymentService.handleWebhookEvent({
type: 'checkout.session.completed',
userId,
subscriptionId: 'sub_123',
});
expect(event).toBeDefined();
});
});

Integration Testing​

  • Test checkout flow end-to-end
  • Test webhook processing
  • Test subscription state transitions
  • Test feature gating
  • Test cancellation flow

Security Considerations​

Webhook Verification​

  • Stripe: Verify webhook signatures using STRIPE_WEBHOOK_SECRET
  • Mock: Skip verification in development, but validate event structure

Payment Data​

  • Never store credit card information
  • Store only subscription IDs and metadata
  • Use payment provider's secure tokenization

User Ownership​

  • Always verify user owns subscription before operations
  • Enforce user isolation in all payment operations
  • Validate userId matches authenticated user

Future Enhancements​

  • Multiple payment providers - PayPal, Apple Pay, Google Pay
  • Subscription tiers - Multiple plan options
  • Promo codes - Discount codes and coupons
  • Gift subscriptions - Gift subscriptions to friends
  • Usage-based billing - For future features
  • Enterprise plans - Team/organization subscriptions

  • Subscription System: See docs/project/TODO.md for subscription feature planning
  • Adapter Pattern: Follows same pattern as EmailService and FileStorageAdapter
  • Database Schema: See Prisma schema for Subscription model

Current Status: Mock implementation complete and fully functional for test deployment. Real payment provider (Stripe) integration deferred until after testing phase.