Skip to content
All essays
WebJanuary 15, 202610 min

Complete Stripe Integration Guide for E-commerce

Master Stripe payment integration in your e-commerce platform with this comprehensive guide covering payments, subscriptions, webhooks, and best practices

Ü
Ümit Uz
Mobile & Full Stack Developer

Stripe has become the go-to payment processor for modern e-commerce platforms. Its robust API, excellent documentation, and powerful features make it ideal for businesses of all sizes. This guide will take you through everything you need to know to integrate Stripe into your e-commerce platform.

Getting Started with Stripe

Account Setup

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

  1. 1Sign up at stripe.com
  2. 2Navigate to the Developers section
  3. 3Get your publishable key (for frontend) and secret key (for backend)
  4. 4Use test mode for development

Install Stripe SDK

bash
npm install stripe @stripe/stripe-js

Backend Integration

Initialize Stripe

typescript
// lib/stripe.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16',
  typescript: true,
});

export default stripe;

Create Payment Intent

Payment Intents are the modern way to handle payments in Stripe:

typescript
// api/create-payment-intent.ts
import stripe from '@/lib/stripe';

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { items, currency = 'usd' } = req.body;

    // Calculate amount
    const amount = items.reduce((total, item) => {
      return total + (item.price * item.quantity);
    }, 0);

    // Create payment intent
    const paymentIntent = await stripe.paymentIntents.create({
      amount: Math.round(amount * 100), // Convert to cents
      currency,
      automatic_payment_methods: {
        enabled: true,
      },
      metadata: {
        order_id: generateOrderId(),
        items: JSON.stringify(items.map(i => ({ id: i.id, qty: i.quantity }))),
      },
    });

    res.json({
      clientSecret: paymentIntent.client_secret,
      paymentIntentId: paymentIntent.id,
    });
  } catch (error) {
    console.error('Payment intent error:', error);
    res.status(500).json({ error: 'Payment processing failed' });
  }
}

Frontend Integration

Stripe Elements

Stripe Elements are pre-built UI components that handle card input securely:

typescript
// components/CheckoutForm.tsx
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { useState } from 'react';

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

export const CheckoutForm = ({ amount, onSuccess }) => {
  const stripe = useStripe();
  const elements = useElements();
  const [processing, setProcessing] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (event) => {
    event.preventDefault();

    if (!stripe || !elements) {
      return;
    }

    setProcessing(true);
    setError(null);

    const cardElement = elements.getElement(CardElement);

    const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
      payment_method: {
        card: cardElement!,
        billing_details: {
          name: 'Customer Name',
          email: 'customer@example.com',
        },
      },
    });

    if (error) {
      setError(error.message || 'Payment failed');
      setProcessing(false);
    } else if (paymentIntent?.status === 'succeeded') {
      onSuccess(paymentIntent);
      setProcessing(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div className="p-4 border rounded-lg">
        <CardElement
          options={{
            style: {
              base: {
                fontSize: '16px',
                color: '#424770',
                '::placeholder': {
                  color: '#aab7c4',
                },
              },
            },
          }}
        />
      </div>

      {error && (
        <div className="text-red-600 text-sm">{error}</div>
      )}

      <button
        type="submit"
        disabled={!stripe || processing}
        className="w-full bg-blue-600 text-white py-3 rounded-lg disabled:bg-gray-400"
      >
        {processing ? 'Processing...' : `Pay $${amount}`}
      </button>
    </form>
  );
};

export const StripeWrapper = ({ amount, onSuccess }) => {
  const [clientSecret, setClientSecret] = useState('');

  useEffect(() => {
    // Fetch payment intent
    fetch('/api/create-payment-intent', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ items: [{ price: amount, quantity: 1 }] }),
    })
      .then(res => res.json())
      .then(data => setClientSecret(data.clientSecret));
  }, [amount]);

  if (!clientSecret) {
    return <div>Loading...</div>;
  }

  return (
    <Elements stripe={stripePromise} options={{ clientSecret }}>
      <CheckoutForm amount={amount} onSuccess={onSuccess} />
    </Elements>
  );
};

Handling Webhooks

Webhooks are crucial for handling asynchronous events from Stripe:

typescript
// api/webhooks/stripe.ts
import stripe from '@/lib/stripe';
import { buffer } from 'micro';

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(req, res) {
  const buf = await buffer(req);
  const sig = req.headers['stripe-signature']!;

  let event;

  try {
    event = stripe.webhooks.constructEvent(
      buf,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    console.log('Webhook signature verification failed.', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object;
      await handleSuccessfulPayment(paymentIntent);
      break;

    case 'payment_intent.payment_failed':
      const failedPayment = event.data.object;
      await handleFailedPayment(failedPayment);
      break;

    case 'charge.refunded':
      const charge = event.data.object;
      await handleRefund(charge);
      break;

    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  res.json({ received: true });
}

async function handleSuccessfulPayment(paymentIntent) {
  const orderId = paymentIntent.metadata.order_id;

  // Update order status
  await updateOrderStatus(orderId, 'paid');

  // Send confirmation email
  await sendOrderConfirmation(orderId);

  // Clear cart
  await clearUserCart(paymentIntent.metadata.user_id);

  console.log(`Payment succeeded for order ${orderId}`);
}

async function handleFailedPayment(paymentIntent) {
  const orderId = paymentIntent.metadata.order_id;

  // Update order status
  await updateOrderStatus(orderId, 'payment_failed');

  // Notify customer
  await notifyPaymentFailed(orderId);

  console.log(`Payment failed for order ${orderId}`);
}

Advanced Features

Subscriptions

For recurring payments, use Stripe Subscriptions:

typescript
// Create subscription
export async function createSubscription(
  customerId: string,
  priceId: string
) {
  const subscription = await stripe.subscriptions.create({
    customer: customerId,
    items: [{ price: priceId }],
    payment_behavior: 'default_incomplete',
    expand: ['latest_invoice.payment_intent'],
  });

  return subscription;
}

// Create customer and subscription
export async function createCustomerAndSubscribe(
  email: string,
  paymentMethodId: string,
  priceId: string
) {
  const customer = await stripe.customers.create({
    email,
    payment_method: paymentMethodId,
    invoice_settings: {
      default_payment_method: paymentMethodId,
    },
  });

  const subscription = await stripe.subscriptions.create({
    customer: customer.id,
    items: [{ price: priceId }],
    expand: ['latest_invoice.payment_intent'],
  });

  return { customer, subscription };
}

Saving Payment Methods

Allow customers to save cards for future purchases:

typescript
// Setup intent for saving card
export async function createSetupIntent(customerId: string) {
  return await stripe.setupIntents.create({
    customer: customerId,
  });
}

// Attach payment method to customer
export async function attachPaymentMethod(
  paymentMethodId: string,
  customerId: string
) {
  return await stripe.paymentMethods.attach(paymentMethodId, {
    customer: customerId,
  });
}

// List customer payment methods
export async function listPaymentMethods(customerId: string) {
  return await stripe.paymentMethods.list({
    customer: customerId,
    type: 'card',
  });
}

Best Practices

1. Never Trust the Client

Always verify amounts and items on the server:

typescript
// ❌ Bad: Trust client data
const amount = req.body.amount;

// ✅ Good: Verify on server
const items = await getOrderItems(req.body.order_id);
const amount = calculateOrderTotal(items);

2. Handle Idempotency

Prevent duplicate charges with idempotency keys:

typescript
const paymentIntent = await stripe.paymentIntents.create(
  { amount: 2000, currency: 'usd' },
  { idempotencyKey: `order-${orderId}` }
);

3. Proper Error Handling

typescript
try {
  const paymentIntent = await stripe.paymentIntents.create(params);
} catch (error) {
  if (error.type === 'StripeCardError') {
    // Card declined
    console.log(error.message);
  } else if (error.type === 'StripeInvalidRequestError') {
    // Invalid parameters
    console.log('Invalid request:', error.message);
  } else {
    // Other error
    console.log('Unexpected error:', error);
  }
}

4. Test Thoroughly

Stripe provides test card numbers for various scenarios:

  • Success: 4242 4242 4242 4242
  • Decline: 4000 0000 0000 0002
  • Insufficient Funds: 4000 0000 0000 9995
  • Expired Card: 4000 0000 0000 0069

Conclusion

Stripe provides a powerful, developer-friendly platform for payment processing. By following this guide and implementing the patterns shown, you'll have a robust payment integration that handles edge cases, provides excellent user experience, and scales with your business.

Remember to always prioritize security, test thoroughly, and stay updated with Stripe's latest features and best practices. Your payment integration is the final and most critical step in the e-commerce journey—get it right, and your customers will thank you with their loyalty.

Enjoyed this essay? Share it.

Related essays

Next essay
Building Live Video Streaming Systems