Integration Guide Sample

Stripe Payment Integration

← Back to Portfolio

Stripe Payment Integration Guide

Introduction

This guide will walk you through integrating Stripe payment processing into your web application. Whether you're building an e-commerce platform, subscription service, or marketplace, you'll learn how to accept payments securely and reliably.

What you'll build:

Estimated time: 2-3 hours for basic integration

Prerequisites:

Overview: How Stripe Payments Work

Understanding the payment flow helps you build a secure integration:

  1. Customer enters payment info on your checkout page (using Stripe.js)
  2. Stripe creates a token representing the payment method (card never touches your server)
  3. Your server receives the token and creates a payment intent
  4. Stripe processes the payment and returns the result
  5. Stripe sends a webhook to confirm payment success
  6. Your application fulfills the order (ships product, grants access, etc.)

This architecture keeps sensitive payment data off your servers and ensures PCI compliance.

Step 1: Set Up Your Stripe Account

Create Your Account

  1. Go to stripe.com and click "Sign Up"
  2. Complete the registration process
  3. Navigate to the Dashboard

Get Your API Keys

Stripe provides two sets of keys:

To find your keys:

  1. In the Stripe Dashboard, click "Developers" in the left sidebar
  2. Click "API keys"
  3. Copy your "Publishable key" and "Secret key"

Important: The publishable key is safe to expose in your frontend code. The secret key must be kept secure on your backend server—never expose it in client-side code.

Step 2: Install Stripe Libraries

Install the Stripe SDK for your backend and frontend:

Backend Installation

Node.js:

npm install stripe

Python:

pip install stripe

Ruby:

gem install stripe

PHP:

composer require stripe/stripe-php

Frontend Installation

Add Stripe.js to your HTML (no installation needed):

<script src="https://js.stripe.com/v3/"></script>

Step 3: Build the Checkout Page

Create a simple checkout form that collects payment information securely.

HTML Structure

<!DOCTYPE html>
<html>
<head>
    <title>Checkout</title>
    <script src="https://js.stripe.com/v3/"></script>
    <style>
        .checkout-form {
            max-width: 500px;
            margin: 50px auto;
            padding: 30px;
            border: 1px solid #ddd;
            border-radius: 8px;
        }
        
        #card-element {
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin-bottom: 20px;
        }
        
        #card-errors {
            color: #dc3545;
            margin-bottom: 20px;
        }
        
        .submit-button {
            width: 100%;
            padding: 15px;
            background: #635bff;
            color: white;
            border: none;
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
        }
        
        .submit-button:hover {
            background: #4f46e5;
        }
        
        .submit-button:disabled {
            background: #ccc;
            cursor: not-allowed;
        }
    </style>
</head>
<body>
    <div class="checkout-form">
        <h2>Complete Your Purchase</h2>
        <p>Total: $49.99</p>
        
        <form id="payment-form">
            <div id="card-element"></div>
            <div id="card-errors"></div>
            <button type="submit" class="submit-button">Pay $49.99</button>
        </form>
    </div>
    
    <script src="checkout.js"></script>
</body>
</html>

JavaScript for Checkout (checkout.js)

// Initialize Stripe with your publishable key
const stripe = Stripe('pk_test_your_publishable_key_here');

// Create card element
const elements = stripe.elements();
const cardElement = elements.create('card', {
    style: {
        base: {
            fontSize: '16px',
            color: '#32325d',
            '::placeholder': {
                color: '#aab7c4',
            },
        },
    },
});

// Mount card element to the page
cardElement.mount('#card-element');

// Handle real-time validation errors
cardElement.on('change', (event) => {
    const displayError = document.getElementById('card-errors');
    if (event.error) {
        displayError.textContent = event.error.message;
    } else {
        displayError.textContent = '';
    }
});

// Handle form submission
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
    event.preventDefault();
    
    // Disable the submit button to prevent duplicate submissions
    const submitButton = form.querySelector('button');
    submitButton.disabled = true;
    submitButton.textContent = 'Processing...';
    
    try {
        // Create payment method
        const {error, paymentMethod} = await stripe.createPaymentMethod({
            type: 'card',
            card: cardElement,
        });
        
        if (error) {
            // Show error to customer
            document.getElementById('card-errors').textContent = error.message;
            submitButton.disabled = false;
            submitButton.textContent = 'Pay $49.99';
            return;
        }
        
        // Send payment method to your server
        const response = await fetch('/create-payment-intent', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                payment_method_id: paymentMethod.id,
                amount: 4999, // Amount in cents
            }),
        });
        
        const data = await response.json();
        
        if (data.error) {
            document.getElementById('card-errors').textContent = data.error;
            submitButton.disabled = false;
            submitButton.textContent = 'Pay $49.99';
            return;
        }
        
        // Payment succeeded
        window.location.href = '/success?payment_intent=' + data.payment_intent_id;
        
    } catch (err) {
        console.error('Payment error:', err);
        document.getElementById('card-errors').textContent = 
            'An unexpected error occurred. Please try again.';
        submitButton.disabled = false;
        submitButton.textContent = 'Pay $49.99';
    }
});

Step 4: Create the Backend Payment Endpoint

Your server needs to create a payment intent and confirm the payment.

Node.js (Express) Example

const express = require('express');
const stripe = require('stripe')('sk_test_your_secret_key_here');

const app = express();
app.use(express.json());

app.post('/create-payment-intent', async (req, res) => {
    try {
        const { payment_method_id, amount } = req.body;
        
        // Validate amount (always validate on the server!)
        if (!amount || amount < 50) {
            return res.status(400).json({ 
                error: 'Invalid amount' 
            });
        }
        
        // Create a payment intent
        const paymentIntent = await stripe.paymentIntents.create({
            amount: amount,
            currency: 'usd',
            payment_method: payment_method_id,
            confirm: true,
            automatic_payment_methods: {
                enabled: true,
                allow_redirects: 'never',
            },
            metadata: {
                order_id: 'order_12345', // Your internal order ID
                customer_email: 'customer@example.com',
            },
        });
        
        res.json({
            success: true,
            payment_intent_id: paymentIntent.id,
        });
        
    } catch (error) {
        console.error('Payment error:', error);
        res.status(400).json({ 
            error: error.message 
        });
    }
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

Python (Flask) Example

from flask import Flask, request, jsonify
import stripe

stripe.api_key = 'sk_test_your_secret_key_here'

app = Flask(__name__)

@app.route('/create-payment-intent', methods=['POST'])
def create_payment():
    try:
        data = request.get_json()
        payment_method_id = data.get('payment_method_id')
        amount = data.get('amount')
        
        # Validate amount
        if not amount or amount < 50:
            return jsonify({'error': 'Invalid amount'}), 400
        
        # Create payment intent
        payment_intent = stripe.PaymentIntent.create(
            amount=amount,
            currency='usd',
            payment_method=payment_method_id,
            confirm=True,
            automatic_payment_methods={
                'enabled': True,
                'allow_redirects': 'never',
            },
            metadata={
                'order_id': 'order_12345',
                'customer_email': 'customer@example.com',
            },
        )
        
        return jsonify({
            'success': True,
            'payment_intent_id': payment_intent.id,
        })
        
    except stripe.error.StripeError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        return jsonify({'error': 'An error occurred'}), 500

if __name__ == '__main__':
    app.run(port=3000)

Step 5: Handle Webhooks for Order Fulfillment

Webhooks ensure you're notified when payments succeed, even if the user closes their browser before the confirmation page loads.

Why Webhooks Matter

Scenario: A customer completes payment but their internet disconnects before your success page loads. Without webhooks, you'd never know the payment succeeded, and the customer wouldn't receive their order.

Solution: Stripe sends webhooks to your server independent of the user's browser, ensuring reliable order fulfillment.

Set Up Your Webhook Endpoint

Node.js:

const endpointSecret = 'whsec_your_webhook_secret_here';

app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
    const sig = req.headers['stripe-signature'];
    
    let event;
    
    try {
        // Verify webhook signature
        event = stripe.webhooks.constructEvent(
            req.body,
            sig,
            endpointSecret
        );
    } 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;
            console.log('Payment succeeded:', paymentIntent.id);
            
            // Fulfill the order
            fulfillOrder(paymentIntent.metadata.order_id);
            break;
            
        case 'payment_intent.payment_failed':
            const failedPayment = event.data.object;
            console.log('Payment failed:', failedPayment.id);
            
            // Notify customer of failure
            notifyCustomer(failedPayment.metadata.customer_email);
            break;
            
        default:
            console.log(`Unhandled event type: ${event.type}`);
    }
    
    res.json({received: true});
});

function fulfillOrder(orderId) {
    // Your order fulfillment logic
    console.log(`Fulfilling order: ${orderId}`);
    // Send email, update database, ship product, etc.
}

function notifyCustomer(email) {
    // Notify customer of payment failure
    console.log(`Notifying customer: ${email}`);
}

Register Your Webhook in Stripe Dashboard

  1. Go to Developers → Webhooks
  2. Click "Add endpoint"
  3. Enter your webhook URL: https://yourdomain.com/webhook
  4. Select events to listen for: payment_intent.succeeded, payment_intent.payment_failed
  5. Copy the webhook signing secret and add it to your code

Test Webhooks Locally

Use the Stripe CLI to forward webhooks to your local development server:

# Install Stripe CLI
brew install stripe/stripe-cli/stripe  # macOS
# or download from stripe.com/docs/stripe-cli

# Login
stripe login

# Forward webhooks to localhost
stripe listen --forward-to localhost:3000/webhook

Security Best Practices

1. Never Store Credit Card Data

DON'T:

// NEVER DO THIS
const cardData = {
    number: req.body.card_number,
    cvc: req.body.cvc,
    // Never handle raw card data
};

DO:

// Always use Stripe.js on the frontend
// Card data goes directly to Stripe, never touches your server
const {paymentMethod} = await stripe.createPaymentMethod({
    type: 'card',
    card: cardElement,
});

2. Validate Everything Server-Side

Critical: Never trust the amount sent from the frontend.

// BAD: Using amount directly from frontend
const amount = req.body.amount; // User could change this!

// GOOD: Look up the real price from your database
const product = await getProductFromDatabase(req.body.product_id);
const amount = product.price;

3. Use HTTPS Everywhere

Stripe requires HTTPS for all payment-related pages. Use a free SSL certificate from Let's Encrypt or your hosting provider.

4. Implement Idempotency

Prevent duplicate payments if a request is retried:

const paymentIntent = await stripe.paymentIntents.create({
    amount: amount,
    currency: 'usd',
    payment_method: payment_method_id,
}, {
    idempotencyKey: orderId, // Prevents duplicate charges
});

5. Verify Webhook Signatures

Always verify that webhooks actually come from Stripe:

try {
    event = stripe.webhooks.constructEvent(
        req.body,
        sig,
        endpointSecret
    );
} catch (err) {
    // Invalid signature - reject the webhook
    return res.status(400).send('Invalid signature');
}

Testing Your Integration

Test Card Numbers

Stripe provides test cards for different scenarios:

Use any future expiration date and any 3-digit CVC.

Test the Complete Flow

  1. Test successful payment:
    • Use test card 4242 4242 4242 4242
    • Complete checkout
    • Verify webhook received
    • Confirm order fulfillment triggered
  2. Test failed payment:
    • Use declined card 4000 0000 0000 0002
    • Verify error message displays
    • Confirm customer not charged
  3. Test webhook delivery:
    • Use Stripe CLI to trigger webhook manually
    • Verify your endpoint handles it correctly

Going to Production

Pre-Launch Checklist

Before accepting real payments, verify:

Switch to Live Mode

  1. In Stripe Dashboard, toggle from "Test mode" to "Live mode"
  2. Generate new live API keys
  3. Update your code with live keys
  4. Register webhook endpoint with live signing secret
  5. Process a small test transaction with a real card
  6. Verify webhook delivery and order fulfillment

Monitor Your Integration

Common Issues and Solutions

Issue: "No such payment_method"

Cause: Payment method ID not created or expired.

Solution: Ensure you're creating the payment method with stripe.createPaymentMethod() before sending it to your server.

Issue: Webhook signature verification fails

Cause: Using wrong signing secret or webhook body was modified.

Solution:

Issue: Duplicate charges

Cause: User clicks "Pay" button multiple times or network retry.

Solution: Implement idempotency keys and disable submit button after first click.

Issue: Payment succeeds but order not fulfilled

Cause: Webhook not received or webhook handler crashed.

Solution:

Additional Resources

Support

Questions about this integration guide? Reach out to discuss your specific implementation needs.

Next Steps