Kulcare Docs
DeveloperSubscriptions

Subscription Plans Feature

Description

The Subscription Plans feature enables dynamic access control across the shell app and all module apps based on a user's subscription status. When a user logs in, their subscription status is retrieved from an API and made globally available. This status determines which actions and features the user can access. If a user attempts a restricted action, they are prompted to upgrade their subscription plan. The solution is designed to be extensible for multiple subscription tiers and feature flags.

Implementation Status ✅

This feature has been fully implemented with all planned phases completed:

  • ✅ Phase 1: Core Infrastructure - API services, types, auth store integration
  • ✅ Phase 2: Access Control System - SubscriptionGate, UpgradeModal components
  • ✅ Phase 3: CMS Integration - Action-level restrictions implemented
  • ✅ Phase 4: Growth Integration - Page-level restrictions implemented
  • ✅ Phase 5: Testing & Polish - Comprehensive test suite and optimizations

Key Points

  • Fetch subscription status on login via API
  • Make status globally available in shell and module apps
  • Restrict actions based on status; prompt upgrade if needed
  • Support multiple tiers and extensibility

Business Requirements

Access Control Logic

  • Read Access: Always allowed - users can view patients, appointments, and all existing data
  • Write Access: Restricted when subscription is expired/inactive
  • Trial Period: 7-day full access trial for all new users
  • Expired Subscriptions: Platform access in read-only mode

CMS Restrictions (Action-Level)

  • No adding new patients to queue
  • No creating new appointments
  • No writing prescriptions
  • All patient intake functionality blocked
  • Existing data viewing remains available

Growth Restrictions (Page-Level)

  • All features restricted when subscription expired
  • Packages feature blocked
  • Google My Business (GMB) management blocked
  • Social media features blocked
  • Analytics viewing blocked

Feature Flag Integration

  • Feature flags override subscription status
  • Admin can enable features regardless of subscription
  • Maintains existing feature flag architecture

Technical Requirements

API Integration

  • Endpoint: GET /clinics/{clinicId}/subscription_status
  • Caching: 24-hour cache with optimized polling strategy
  • Real-time Updates: Handle subscription changes via custom events
  • Error Handling: Graceful fallback with defensive programming
  • Mock Support: Full mock API for development and testing

State Management

  • Extend existing Zustand auth store
  • Clinic-specific subscription state
  • Module Federation sharing across micro-frontends
  • Support for feature-specific paid add-ons

Access Control Implementation

  • Action-Level: Wrap individual CTAs in SubscriptionGate component
  • Page-Level: Consistent approach for Growth module
  • Graceful Degradation: Show upgrade prompts, not broken features
  • Modal-Based Upgrades: Centralized upgrade flow

Api for subscription status

GET https://api-stg.kulcare.com/clinics/201090/subscription_status

Api response

{
  "success": true,
  "message": "Data fetched",
  "data": {
    "is_trial": false,
    "razorpay_subscription_id": "sub_Q3nyWt78XfG5C3",
    "subscription_status": "active",
    "subscription_start_date": "2025-03-07T06:41:09.015Z",
    "next_billing_date": "2026-03-06T18:30:00.000Z",
    "subscription": {
      "id": 430,
      "billing_plan_id": 24,
      "billing_customer_id": 550,
      "razorpay_subscription_id": "sub_Q3nyWt78XfG5C3",
      "basic_billing_subscription_status_id": 3,
      "current_start": "2025-03-07T06:43:13.000Z",
      "current_end": "2026-03-06T18:30:00.000Z",
      "charge_at": "2026-03-06T18:30:00.000Z",
      "end_at": "2048-03-06T18:30:00.000Z",
      "auth_attempts": 0,
      "created_at": "2025-03-07T06:41:09.015Z",
      "updated_at": "2025-03-07T06:44:51.640Z",
      "expire_by": "2026-03-07T06:41:06.000Z",
      "cancelled": false,
      "billing_promo_code_id": null,
      "cancelled_at": null,
      "stripe_checkout_session_id": null,
      "stripe_checkout_url": null,
      "stripe_subscription_id": null,
      "billing_subscription_status": "active",
      "plan": {
        "id": 24,
        "name": "Premium",
        "description": "For new clinics",
        "package_features": [
          "Get listed on kulcare",
          "Unlimited Patients per day",
          "Multiple Locations, Doctors \u0026 Staff",
          "Queue \u0026 Appointments",
          "Smart Billing",
          "Video Consultations**",
          "Digital Prescriptions",
          "Free Patient Mobile Apps",
          "Clinic Website with up to 40 themes on your domain",
          "Manage Google My Business",
          "Patient Analytics included",
          "10 teleconsult free without fee per month",
          "Packages creations for promotions"
        ],
        "interval": 12,
        "active": true,
        "razorpay_plan_id": "plan_MuoApZqFDLzkhd",
        "alpha_2_code": "IN",
        "stripe_price_id": null,
        "stripe_payment_link": null,
        "visible": true,
        "amount": "9888.00",
        "base_amount": "9888.00",
        "period": "monthly",
        "currency": "INR",
        "note": null,
        "limit": [
          {
            "limit_type": null,
            "limit_quantity": null
          },
          {
            "limit_type": null,
            "limit_quantity": null
          },
          {
            "limit_type": null,
            "limit_quantity": null
          },
          {
            "limit_type": null,
            "limit_quantity": null
          },
          {
            "limit_type": "FREE_TELECONSULTATIONS_COUNT",
            "limit_quantity": 10
          },
          {
            "limit_type": null,
            "limit_quantity": null
          }
        ],
        "purchased": true,
        "address_present": false,
        "products": [
          {
            "id": 1,
            "name": "CMS"
          },
          {
            "id": 2,
            "name": "GROWTH"
          },
          {
            "id": 4,
            "name": "ANALYTICS"
          },
          {
            "id": 5,
            "name": "PACKAGES"
          },
          {
            "id": 6,
            "name": "GMB"
          }
        ]
      }
    }
  }
}

Implementation Plan

Phase 1: Core Infrastructure (Days 1-2)

1. Subscription API Service (packages/api-client/src/services/subscriptionApi.ts)

  • Fetch subscription status with clinic-specific endpoint
  • Implement 24-hour cache refresh strategy
  • Handle API errors gracefully with fallback behavior
  • Support both mock and real API modes

2. Subscription Types (packages/api-client/src/types/subscription.ts)

  • Complete TypeScript interfaces for API response
  • Subscription status enums (active, expired, trial, cancelled)
  • Plan details, product access, and billing information
  • Usage limits and feature restrictions

3. Enhanced Auth Store (packages/auth/src/store.ts)

  • Add subscription state to existing Zustand store
  • Implement clinic-specific subscription caching
  • Auto-refresh on clinic selection
  • Real-time status update handling

4. Subscription Hook (packages/auth/src/hooks/useSubscription.ts)

  • useSubscription() - Get current subscription status
  • useFeatureAccess(feature) - Check specific feature availability
  • useProductAccess(product) - Check product-level access
  • Real-time subscription updates support

Phase 2: Access Control System (Days 3-4)

1. SubscriptionGate Component (packages/ui-kit/src/components/subscription/SubscriptionGate.tsx)

  • Wrapper component for protected features
  • Props: feature, product, children, fallback
  • Shows children when allowed, upgrade prompt when restricted
  • Configurable for different restriction types

2. UpgradeModal Component (packages/ui-kit/src/components/subscription/UpgradeModal.tsx)

  • Centralized upgrade prompt modal
  • Clear messaging about restricted features
  • Integration with billing/upgrade flow
  • Triggered from multiple locations

3. SubscriptionStatus Component (packages/ui-kit/src/components/subscription/SubscriptionStatus.tsx)

  • Display current plan information
  • Trial countdown for trial users
  • Expiration warnings
  • Quick upgrade access

4. Module Federation Setup

  • Share subscription context as singleton
  • Configure in vite.config.ts for all apps
  • Ensure consistent state across micro-frontends

Phase 3: CMS Integration (Days 5-6)

1. Patient Management Updates

  • Wrap "Add Patient" buttons in SubscriptionGate
  • Restrict patient intake forms
  • Maintain read access to patient list

2. Queue Management Updates

  • Gate "Add to Queue" functionality
  • Block queue modifications
  • Allow viewing queue status

3. Appointment Updates

  • Restrict "New Appointment" creation
  • Block appointment modifications
  • Maintain calendar viewing

4. Prescription Updates

  • Gate prescription writing features
  • Block medication modifications
  • Allow viewing existing prescriptions

Phase 4: Growth Integration (Day 7)

1. Page-Level Restrictions

  • Implement consistent gating approach
  • Block access to all Growth features when expired
  • Show upgrade prompts on restricted pages

2. Feature-Specific Gates

  • Packages management
  • GMB integration
  • Social media features
  • Analytics dashboards

Phase 5: Testing & Polish (Days 8-9)

1. Testing Scenarios

  • Trial period functionality (7-day countdown)
  • Expired subscription read-only mode
  • Real-time subscription updates
  • Feature flag override behavior

2. Error Handling

  • API failure scenarios
  • Network connectivity issues
  • Invalid subscription states
  • Cache invalidation

3. User Experience

  • Smooth upgrade flow
  • Clear restriction messaging
  • Consistent UI/UX across apps
  • Performance optimization

Component Structure

SubscriptionGate Usage Example

<SubscriptionGate feature="patient.create" product="CMS">
  <Button onClick={handleAddPatient}>Add Patient</Button>
</SubscriptionGate>

useSubscription Hook Example

const { subscription, isActive, isTrial, daysRemaining, hasProduct } =
  useSubscription();

const canCreateAppointment = hasProduct("CMS") && isActive;

UpgradeModal Integration

const { showUpgradeModal } = useUpgradeModal();

const handleRestrictedAction = () => {
  showUpgradeModal({
    feature: "Digital Prescriptions",
    message: "Upgrade to Premium to write prescriptions",
  });
};

Implementation Details

Subscription Hook (packages/auth/src/hooks/useSubscription.ts)

The core hook provides comprehensive subscription management:

export function useSubscription() {
  const {
    subscription,
    subscriptionLoading,
    subscriptionError,
    lastSubscriptionFetch,
    fetchSubscription,
  } = useAuth();

  // Computed properties
  const isActive = subscription?.subscription_status === "active";
  const isTrial = subscription?.is_trial === true;
  const isExpired = subscription?.subscription_status === "expired";
  const isCancelled = subscription?.subscription_status === "cancelled";

  // Calculate days remaining for trial
  const daysRemaining = useMemo(() => {
    if (!isTrial || !subscription?.subscription?.current_end) return null;
    // Calculate days until trial end
  }, [isTrial, subscription]);

  // Feature access checks
  const hasFeatureAccess = (feature: string) => {
    if (!isActive && !isTrial) return false;
    // Check specific feature access
  };

  return {
    subscription,
    isActive,
    isTrial,
    isExpired,
    isCancelled,
    daysRemaining,
    hasFeatureAccess,
    // ... more utilities
  };
}

SubscriptionGate Component

Provides flexible access control:

interface SubscriptionGateProps {
  feature?: string;
  product?: string;
  children: React.ReactNode;
  fallback?: React.ReactNode;
  onRestricted?: () => void;
  showUpgradePrompt?: boolean;
}

export function SubscriptionGate({
  feature,
  product,
  children,
  fallback,
  onRestricted,
  showUpgradePrompt = true,
}: SubscriptionGateProps) {
  const { isActive, isTrial, hasFeatureAccess } = useSubscription();
  const { showUpgradeModal } = useUpgradeModal();

  const hasAccess = isActive || isTrial;

  if (!hasAccess) {
    if (onRestricted) {
      onRestricted();
    }
    if (showUpgradePrompt) {
      // Show upgrade modal
    }
    return fallback || null;
  }

  return <>{children}</>;
}

Upgrade Modal System

Centralized upgrade prompts with context-aware messaging:

interface UpgradeModalContextValue {
  showUpgradeModal: (options?: UpgradeModalOptions) => void;
  hideUpgradeModal: () => void;
  isOpen: boolean;
}

export function UpgradeModalProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const [isOpen, setIsOpen] = useState(false);
  const [options, setOptions] = useState<UpgradeModalOptions>();

  const showUpgradeModal = useCallback((opts?: UpgradeModalOptions) => {
    setOptions(opts);
    setIsOpen(true);
  }, []);

  // Modal renders with appropriate messaging based on context
}

24-Hour Caching Strategy

Subscription data is cached for 24 hours with smart invalidation:

// In auth store
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours

const shouldRefetchSubscription = () => {
  const { lastSubscriptionFetch } = get();
  if (!lastSubscriptionFetch) return true;

  const timeSinceLastFetch = Date.now() - lastSubscriptionFetch;
  return timeSinceLastFetch > CACHE_DURATION;
};

// Automatic refresh on clinic selection
selectClinic: (clinic: Clinic) => {
  set({ selectedClinic: clinic });

  // Refresh subscription for new clinic
  if (get().isAuthenticated && shouldRefetchSubscription()) {
    fetchSubscription();
  }
};

Real-time Updates via Events

Subscription changes are propagated via custom events:

// Dispatch subscription update event
window.dispatchEvent(
  new CustomEvent("subscription-updated", {
    detail: { subscription: newSubscriptionData },
  })
);

// Listen for updates in components
useEffect(() => {
  const handleSubscriptionUpdate = (event: CustomEvent) => {
    // Update local state or refetch
  };

  window.addEventListener("subscription-updated", handleSubscriptionUpdate);
  return () => {
    window.removeEventListener(
      "subscription-updated",
      handleSubscriptionUpdate
    );
  };
}, []);

Testing Implementation

Mock Data Support

Complete mock API for development:

// packages/mock-data/src/subscription/subscriptionStatus.ts
export const mockSubscriptionStatus = {
  active: createMockSubscription("active", false),
  trial: createMockSubscription("active", true),
  expired: createMockSubscription("expired", false),
  cancelled: createMockSubscription("cancelled", false),
};

Test Scenarios

Comprehensive test coverage including:

  • Trial period countdown functionality
  • Expired subscription read-only mode
  • Real-time subscription updates
  • Feature flag override behavior
  • API failure scenarios
  • Cache invalidation

Migration Notes

Billing Page Implementation

The billing functionality has been fully implemented in the shell app:

  • Account Overview (/account) - Subscription status and quick actions
  • Subscription Management (/account/subscription) - Plan details and upgrades
  • Billing Information (/account/billing-address) - Company details and tax info. Editing needs manage_tax_identity (owner-only), the same key Billing::BillingAddressPolicy checks (#2338)
  • Payment Methods (/account/payment-methods) - Card management
  • Invoices (/account/invoices) - Invoice history and downloads

Integration Points

  • CMS app uses SubscriptionGate for action-level restrictions
  • Growth app implements page-level access control
  • Shell app manages all billing and subscription operations
  • Shared state via Module Federation ensures consistency

On this page

DescriptionImplementation Status ✅Key PointsBusiness RequirementsAccess Control LogicCMS Restrictions (Action-Level)Growth Restrictions (Page-Level)Feature Flag IntegrationTechnical RequirementsAPI IntegrationState ManagementAccess Control ImplementationApi for subscription statusApi responseImplementation PlanPhase 1: Core Infrastructure (Days 1-2)1. Subscription API Service (packages/api-client/src/services/subscriptionApi.ts)2. Subscription Types (packages/api-client/src/types/subscription.ts)3. Enhanced Auth Store (packages/auth/src/store.ts)4. Subscription Hook (packages/auth/src/hooks/useSubscription.ts)Phase 2: Access Control System (Days 3-4)1. SubscriptionGate Component (packages/ui-kit/src/components/subscription/SubscriptionGate.tsx)2. UpgradeModal Component (packages/ui-kit/src/components/subscription/UpgradeModal.tsx)3. SubscriptionStatus Component (packages/ui-kit/src/components/subscription/SubscriptionStatus.tsx)4. Module Federation SetupPhase 3: CMS Integration (Days 5-6)1. Patient Management Updates2. Queue Management Updates3. Appointment Updates4. Prescription UpdatesPhase 4: Growth Integration (Day 7)1. Page-Level Restrictions2. Feature-Specific GatesPhase 5: Testing & Polish (Days 8-9)1. Testing Scenarios2. Error Handling3. User ExperienceComponent StructureSubscriptionGate Usage ExampleuseSubscription Hook ExampleUpgradeModal IntegrationImplementation DetailsSubscription Hook (packages/auth/src/hooks/useSubscription.ts)SubscriptionGate ComponentUpgrade Modal System24-Hour Caching StrategyReal-time Updates via EventsTesting ImplementationMock Data SupportTest ScenariosMigration NotesBilling Page ImplementationIntegration Points