/web-app-features

How to Add In-App Purchases to Your Web App

Learn how to easily add in-app purchases to your web app with our step-by-step guide for seamless monetization.

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Add In-App Purchases to Your Web App

Adding In-App Purchases to Your Web App: A Complete Implementation Guide

 

Introduction to In-App Purchases for Web Applications

 

Implementing in-app purchases in your web application can transform your revenue model and user experience. Unlike mobile apps with standardized stores, web apps require custom implementation approaches. This guide walks you through the process of adding robust, secure in-app purchases to your web application.

 

Understanding Your Payment Options

 

Choose the Right Payment Processing Approach

 

  • Direct payment processors (Stripe, PayPal, Square)
  • Subscription management platforms (Chargebee, Recurly)
  • Platform-specific options (Apple Pay for web, Google Pay)

 

The most common approach for web apps is using Stripe or similar processors that handle the payment infrastructure while giving you control over the experience. Let's focus on implementing this pattern.

 

Implementation Steps

 

Step 1: Design Your Purchase Flow

 

Before coding, map your purchase flow. Consider:

  • Where in the app will purchases be triggered?
  • What happens immediately after purchase?
  • How will users manage their subscriptions later?

 

Step 2: Set Up Your Payment Processor (Using Stripe as Example)

 

First, create a Stripe account and retrieve your API keys:

// Backend initialization (Node.js example)
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');

 

Step 3: Define Your Products and Pricing

 

Configure your products in your payment processor dashboard or via API:

// Creating a product and price programmatically
const product = await stripe.products.create({
  name: 'Pro Plan',
  description: 'Access to all premium features',
});

const price = await stripe.prices.create({
  product: product.id,
  unit_amount: 1999, // $19.99
  currency: 'usd',
  recurring: {
    interval: 'month',
  },
});

 

Step 4: Implement Frontend Payment UI

 

Add the payment interface in your application:

<!-- Simple payment button -->
<button id="checkout-button" class="payment-button">
  Upgrade to Pro Plan
</button>

<script>
  document.getElementById('checkout-button').addEventListener('click', function() {
    // Call your backend to create a checkout session
    fetch('/create-checkout-session', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        priceId: 'price_1234567890',
      })
    })
    .then(function(response) {
      return response.json();
    })
    .then(function(session) {
      // Redirect to Stripe Checkout
      return stripe.redirectToCheckout({ sessionId: session.id });
    })
    .catch(function(error) {
      console.error('Error:', error);
    });
  });
</script>

 

Step 5: Create Backend Endpoints

 

Set up the server endpoints to handle payment processing:

// Express.js example
app.post('/create-checkout-session', async (req, res) => {
  const { priceId } = req.body;
  
  // Get the authenticated user
  const userId = req.user.id;
  
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [
        {
          price: priceId,
          quantity: 1,
        },
      ],
      mode: 'subscription',
      success_url: `${YOUR_DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${YOUR_DOMAIN}/canceled`,
      client_reference_id: userId, // To identify the customer in webhooks
    });

    res.json({ id: session.id });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

 

Step 6: Implement Webhook Handling

 

Set up webhooks to receive real-time payment events:

// Webhook endpoint to handle subscription events
app.post('/webhook', async (req, res) => {
  const signature = req.headers['stripe-signature'];
  let event;

  try {
    // Verify the event came from Stripe
    event = stripe.webhooks.constructEvent(
      req.rawBody, // You need to configure your framework to expose the raw body
      signature,
      'whsec_your_webhook_signing_secret'
    );
  } catch (err) {
    console.log(`⚠️ Webhook signature verification failed: ${err.message}`);
    return res.sendStatus(400);
  }

  // Handle specific events
  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object;
      // Retrieve customer ID from client_reference_id
      const userId = session.client_reference_id;
      
      // Activate subscription for user
      await activateSubscription(userId, session.subscription);
      break;
    }
    case 'invoice.paid': {
      // Continue subscription
      const invoice = event.data.object;
      await extendSubscription(invoice.subscription);
      break;
    }
    case 'invoice.payment_failed': {
      // Handle failed payment
      const invoice = event.data.object;
      await handleFailedPayment(invoice.subscription);
      break;
    }
    // Add more event handlers as needed
  }

  res.sendStatus(200);
});

// Functions to update your database
async function activateSubscription(userId, subscriptionId) {
  // Update user record in your database
  await db.users.update({
    where: { id: userId },
    data: { 
      subscriptionId: subscriptionId,
      subscriptionStatus: 'active',
      planType: 'pro',
      // Add additional fields as needed
    }
  });
}

 

Step 7: Store Purchase State in Your Database

 

Create database tables to track subscription status:

-- Example schema for subscription tracking
CREATE TABLE user_subscriptions (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id),
  subscription_id VARCHAR(255) NOT NULL,
  plan_type VARCHAR(50) NOT NULL,
  status VARCHAR(50) NOT NULL,
  current_period_start TIMESTAMP NOT NULL,
  current_period_end TIMESTAMP NOT NULL,
  cancel_at_period_end BOOLEAN DEFAULT FALSE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

 

Step 8: Implement Feature Access Control

 

Create middleware or services to check subscription status before allowing access to premium features:

// Example middleware for checking subscription
function requireSubscription(planLevel = 'basic') {
  return async (req, res, next) => {
    const user = req.user;
    
    // If user isn't logged in
    if (!user) {
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    // Fetch latest subscription data
    const subscription = await getSubscriptionStatus(user.id);
    
    if (!subscription || subscription.status !== 'active') {
      return res.status(403).json({ error: 'Active subscription required' });
    }
    
    // Check plan level if specified
    if (planLevel !== 'basic' && subscription.planType !== planLevel) {
      return res.status(403).json({ 
        error: `${planLevel} subscription required` 
      });
    }
    
    // Add subscription to request object for later use
    req.subscription = subscription;
    next();
  };
}

// Usage in routes
app.get('/premium-content', requireSubscription('pro'), (req, res) => {
  // Handle premium content request
  res.json({ premiumContent: 'This is exclusive content' });
});

 

Step 9: Add Subscription Management UI

 

Create interfaces for users to manage their subscriptions:

// Generate a customer portal session
app.post('/create-customer-portal-session', async (req, res) => {
  const userId = req.user.id;
  const userRecord = await db.users.findUnique({
    where: { id: userId }
  });
  
  // Create a portal session
  const session = await stripe.billingPortal.sessions.create({
    customer: userRecord.stripeCustomerId,
    return_url: `${YOUR_DOMAIN}/account`,
  });
  
  // Return the URL to the portal
  res.json({ url: session.url });
});

 

Step 10: Implement Analytics and Reporting

 

Track purchase metrics to optimize your business:

// Example function to gather subscription metrics
async function getSubscriptionMetrics() {
  const now = new Date();
  const thirtyDaysAgo = new Date(now.setDate(now.getDate() - 30));
  
  // Active subscriptions
  const activeSubscriptions = await db.userSubscriptions.count({
    where: { status: 'active' }
  });
  
  // New subscriptions in last 30 days
  const newSubscriptions = await db.userSubscriptions.count({
    where: { 
      created_at: { gte: thirtyDaysAgo },
      status: 'active'
    }
  });
  
  // Churn rate calculation
  const cancelledSubscriptions = await db.userSubscriptions.count({
    where: {
      updated_at: { gte: thirtyDaysAgo },
      status: 'cancelled'
    }
  });
  
  const churnRate = activeSubscriptions > 0 
    ? (cancelledSubscriptions / activeSubscriptions) * 100 
    : 0;
  
  return {
    activeSubscriptions,
    newSubscriptions,
    cancelledSubscriptions,
    churnRate: `${churnRate.toFixed(2)}%`
  };
}

 

Advanced Considerations

 

Handling Subscription Lifecycle Events

 

Create comprehensive handlers for all subscription states:

// Handle various subscription states
async function handleSubscriptionUpdated(subscription) {
  const status = subscription.status;
  const userId = await getUserIdFromSubscription(subscription.id);
  
  switch (status) {
    case 'active':
      await updateUserPlan(userId, 'active', subscription.items.data[0].price.product);
      await sendEmail(userId, 'subscription_activated');
      break;
    case 'past_due':
      await updateUserPlan(userId, 'past_due');
      await sendEmail(userId, 'payment_failed');
      break;
    case 'canceled':
      await updateUserPlan(userId, 'canceled');
      await scheduleFeatureDeactivation(userId, subscription.current_period_end);
      await sendEmail(userId, 'subscription_canceled');
      break;
    // Handle other statuses
  }
}

 

Implementing Promotional Offers

 

Add support for discounts and trial periods:

// Create a checkout session with a coupon
app.post('/create-checkout-session-with-coupon', async (req, res) => {
  const { priceId, couponId } = req.body;
  const userId = req.user.id;
  
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [
        {
          price: priceId,
          quantity: 1,
        },
      ],
      discounts: [
        {
          coupon: couponId,
        },
      ],
      mode: 'subscription',
      success_url: `${YOUR_DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${YOUR_DOMAIN}/canceled`,
      client_reference_id: userId,
    });

    res.json({ id: session.id });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

 

Implementing One-Time Purchases

 

For products that aren't subscription-based:

// Create a one-time purchase checkout
app.post('/create-one-time-checkout', async (req, res) => {
  const { priceId } = req.body;
  const userId = req.user.id;
  
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [
        {
          price: priceId,
          quantity: 1,
        },
      ],
      mode: 'payment', // One-time payment
      success_url: `${YOUR_DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${YOUR_DOMAIN}/canceled`,
      client_reference_id: userId,
    });

    res.json({ id: session.id });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

 

Testing Your Implementation

 

Test Cards and Workflows

 

Use these Stripe test cards to verify different scenarios:

// Test card numbers to use in development
const testCards = {
  success: '4242 4242 4242 4242', // Always succeeds
  requiresAuth: '4000 0025 0000 3155', // Requires authentication
  declinedCard: '4000 0000 0000 0002', // Always fails
  insufficientFunds: '4000 0000 0000 9995' // Insufficient funds failure
};

// Test a subscription renewal failure
async function testFailedRenewal(subscriptionId) {
  // This creates a failed invoice
  const invoice = await stripe.invoices.create({
    customer: customerId,
    subscription: subscriptionId,
  });
  
  await stripe.invoices.finalizeInvoice(invoice.id);
  
  // This will trigger your webhook with invoice.payment_failed
  await stripe.invoices.pay(invoice.id, {
    paid_out_of_band: false
  }).catch(err => console.log('Expected error:', err.message));
}

 

Common Pitfalls and How to Avoid Them

 

Security Considerations

 

  • Always verify webhook signatures to prevent fraudulent events
  • Never store raw credit card data in your database
  • Implement proper access controls for subscription management
  • Use HTTPS for all payment-related communications

 

Handling Edge Cases

 

  • Payment method expiration
  • Currency conversion for international customers
  • Subscription pausing and resuming
  • Refund processing

 

// Example: Handle subscription pause request
app.post('/pause-subscription', requireAuth, async (req, res) => {
  const { subscriptionId, resumeDate } = req.body;
  const userId = req.user.id;
  
  // Verify user owns this subscription
  const subscription = await db.userSubscriptions.findFirst({
    where: {
      user_id: userId,
      subscription_id: subscriptionId
    }
  });
  
  if (!subscription) {
    return res.status(404).json({ error: 'Subscription not found' });
  }
  
  try {
    // Pause the subscription
    await stripe.subscriptions.update(subscriptionId, {
      pause_collection: {
        behavior: 'void', // or 'keep_as_draft' to still generate invoices
        resumes_at: Math.floor(new Date(resumeDate).getTime() / 1000),
      },
    });
    
    // Update local database
    await db.userSubscriptions.update({
      where: { id: subscription.id },
      data: { 
        status: 'paused',
        resume_at: resumeDate
      }
    });
    
    res.json({ message: 'Subscription paused successfully' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

 

Conclusion

 

Implementing in-app purchases for your web application requires careful planning and attention to detail, but can transform your business model. The implementation patterns above provide a solid foundation that you can adapt to your specific needs.

Remember that the most successful in-app purchase systems are those that blend seamlessly with your user experience. Keep the purchase flow simple, provide clear value propositions, and make subscription management intuitive.

By following these patterns and being mindful of the edge cases, you'll create a robust system that can scale with your business while providing a smooth experience for your users.

Ship In-App Purchases 10x Faster with RapidDev

Connect with our team to unlock the full potential of code solutions with a no-commitment consultation!

Book a Free Consultation

Top 3 In-App Purchases Usecases

Explore the top 3 in-app purchase use cases to boost revenue and enhance user experience in your web app.

 

Premium Content Unlock

 

Enables access to exclusive or advanced functionality within an application after purchase. Content-as-a-product model creates sustainable revenue streams while maintaining a free entry point for new users.

 

  • Implementation complexity: Moderate - requires content segmentation, purchase validation, and persistent state management
  • Revenue potential: High for apps with compelling premium content that delivers clear value beyond the free experience
  • Business impact: Balances user acquisition with monetization by allowing users to "try before they buy" while creating ongoing revenue opportunities

 

Subscription Services

 

Provides recurring access to continually updated content, features, or services. Creates predictable revenue streams and incentivizes ongoing product development to maintain subscriber retention.

 

  • Implementation complexity: High - requires subscription management, renewal handling, and cross-platform entitlement tracking
  • Revenue potential: Exceptional for apps delivering consistent value, with lifetime user value often 3-10× higher than one-time purchases
  • Business impact: Transforms business models from sporadic income to predictable revenue forecasting, enabling more strategic investment in product development

 

Consumable Virtual Goods

 

Offers limited-use items that enhance the user experience and require repurchase after consumption. Creates renewable purchase opportunities while maintaining app accessibility for non-paying users.

 

  • Implementation complexity: Moderate-to-high - requires inventory management, purchase verification, and consumption tracking
  • Revenue potential: Variable but potentially unlimited, as it enables repeated purchases from engaged users
  • Business impact: Provides flexible monetization that scales with user engagement while allowing fine-tuned balance between free and premium experiences


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.