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