6 min read

Building a SaaS Product with Stripe

This tutorial walks you through building a complete subscription-based SaaS product using Stripe and iaptic.

Part 1: Project Setup

Initial Configuration

// 1. Install dependencies
npm install @iaptic/stripe-client express typescript

// 2. Initialize iaptic
const iaptic = new IapticStripe({
  stripePublicKey: process.env.STRIPE_PUBLIC_KEY,
  appName: 'com.example.saas',
  apiKey: process.env.IAPTIC_API_KEY
});

User Management

interface User {
  id: string;
  email: string;
  subscriptionId?: string;
  accessKey?: string;
  features: string[];
}

class UserManager {
  async createUser(email: string): Promise<User> {
    // Implementation
  }

  async getSubscriptionStatus(user: User): Promise<SubscriptionStatus> {
    // Implementation
  }
}

Part 2: Subscription Management

Product Configuration

// In Stripe Dashboard, configure products:
{
  "Basic Plan": {
    metadata: {
      product_type: "paid subscription",
      features: "basic,support",
      tier: "basic",
      max_users: "5"
    }
  },
  "Pro Plan": {
    metadata: {
      product_type: "paid subscription", 
      features: "basic,advanced,api,support",
      tier: "pro",
      max_users: "20"
    }
  }
}

Checkout Flow

class SubscriptionManager {
  async startSubscription(user: User, planId: string) {
    const response = await iaptic.createStripeCheckout({
      offerId: planId,
      applicationUsername: user.id,
      successUrl: `${DOMAIN}/subscription/success`,
      cancelUrl: `${DOMAIN}/subscription/cancel`
    });

    await this.storeAccessKey(user.id, response.accessKey);
    return response.url;
  }
}

Part 3: Access Control

Feature Management

class FeatureManager {
  async getFeatures(user: User): Promise<string[]> {
    const subscription = await iaptic.getPurchases(
      user.subscriptionId,
      user.accessKey
    );
    
    if (!subscription.purchases.length) return [];
    
    return subscription.purchases[0].metadata?.features?.split(',') ?? [];
  }

  canAccess(user: User, feature: string): boolean {
    return user.features.includes(feature);
  }
}

Middleware

function requireFeature(feature: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const user = req.user as User;
    if (!featureManager.canAccess(user, feature)) {
      return res.status(403).json({
        error: 'Upgrade required',
        requiredFeature: feature
      });
    }
    next();
  };
}

Part 4: Subscription Lifecycle

Webhook Processing

app.post('/webhook/stripe', async (req, res) => {
  const event = verifyWebhookSignature(req);
  
  switch (event.type) {
    case 'customer.subscription.updated':
      await handleSubscriptionUpdate(event);
      break;
    case 'customer.subscription.deleted':
      await handleSubscriptionCanceled(event);
      break;
  }
  
  res.json({ ok: true });
});

Status Updates

async function handleSubscriptionUpdate(event: StripeEvent) {
  const subscription = event.data.object;
  const user = await getUserBySubscriptionId(subscription.id);
  
  // Update user features
  user.features = subscription.metadata.features.split(',');
  await updateUser(user);
  
  // Notify user
  await sendEmail(user.email, 'Subscription Updated', {
    features: user.features
  });
}

Part 5: User Experience

Customer Portal

class CustomerPortal {
  async redirectToPortal(user: User) {
    return await iaptic.redirectToCustomerPortal({
      id: user.subscriptionId,
      accessKey: user.accessKey,
      returnUrl: `${DOMAIN}/account`
    });
  }
}

Plan Changes

class PlanManager {
  async changePlan(user: User, newPlanId: string) {
    const response = await iaptic.changePlan({
      id: user.subscriptionId,
      accessKey: user.accessKey,
      offerId: newPlanId
    });
    
    if (response.new_access_keys) {
      await this.updateAccessKey(
        user.id, 
        response.new_access_keys[user.subscriptionId]
      );
    }
    
    return response;
  }
}

Part 6: Testing & Monitoring

Test Environment

class TestEnvironment {
  async createTestSubscription() {
    // Create test user
    const user = await userManager.createUser('[email protected]');
    
    // Start subscription with test card
    const subscription = await subscriptionManager.startSubscription(
      user,
      'stripe:prod_test#price_test'
    );
    
    return { user, subscription };
  }
}

Monitoring

class SubscriptionMonitor {
  async checkHealth() {
    // Check webhook deliveries
    const failedWebhooks = await getFailedWebhooks();
    
    // Verify access keys
    const expiredKeys = await findExpiredAccessKeys();
    
    // Monitor subscription status
    const problemSubscriptions = await findProblemSubscriptions();
    
    return { failedWebhooks, expiredKeys, problemSubscriptions };
  }
}

Complete Example

See the example repository for a working implementation including:

  • User authentication
  • Subscription management
  • Feature flags
  • Webhook handling
  • Error recovery
  • Testing suite