/stripe-guides

How to use Stripe API with Node.js?

Learn how to integrate Stripe API with Node.js: set up your account, process payments, handle webhooks, manage subscriptions, and implement customer management.

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.

Book a free consultation

How to use Stripe API with Node.js?

How to Use Stripe API with Node.js

 

Introduction

 

This tutorial will guide you through the process of integrating Stripe's payment processing API with a Node.js application. We'll cover setting up your Stripe account, installing necessary dependencies, implementing basic payment functionality, and handling common use cases.

 

Step 1: Set Up Your Stripe Account

 

Before you can start integrating Stripe with Node.js, you need to create and set up your Stripe account:

  1. Go to Stripe's website and sign up for an account
  2. Verify your email address and complete the initial setup
  3. Navigate to the Developers section in your Stripe dashboard
  4. Locate your API keys (both publishable and secret keys)

Note: Stripe provides both test and live keys. Always use test keys during development to avoid processing real payments.

 

Step 2: Set Up Your Node.js Project

 

Let's create a new Node.js project and install the necessary dependencies:


mkdir stripe-node-project
cd stripe-node-project
npm init -y
npm install stripe express body-parser dotenv

Create a basic project structure:


touch server.js
touch .env
mkdir public
touch public/index.html
touch public/script.js
touch public/style.css

 

Step 3: Configure Environment Variables

 

Create a .env file to securely store your Stripe API keys:


STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key
STRIPE_SECRET_KEY=sk_test_your_secret_key

Make sure to replace the placeholder values with your actual test API keys from your Stripe dashboard.

 

Step 4: Set Up a Basic Express Server

 

Create a basic Express server in your server.js file:


// Import required dependencies
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const path = require('path');

// Initialize Express app
const app = express();
const PORT = process.env.PORT || 3000;

// Middleware setup
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(\_\_dirname, 'public')));

// Routes will be added here

// Start the server
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

 

Step 5: Create a Basic Frontend

 

Let's create a simple payment form in your public/index.html file:





  
  
  Stripe Payment Example
  
  


  

Stripe Payment Demo

Add some basic styles in public/style.css:


body {
  font-family: 'Arial', sans-serif;
  line-height: 1.6;
  margin: 0;
  padding: 20px;
  background-color: #f7f9fc;
}

.container {
  max-width: 600px;
  margin: 0 auto;
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.form-group {
  margin-bottom: 20px;
}

label {
  display: block;
  margin-bottom: 10px;
  font-weight: bold;
}

#card-element {
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  padding: 12px;
  background-color: #fafafa;
}

#card-errors {
  color: #fa755a;
  margin-top: 10px;
  font-size: 14px;
}

button {
  background-color: #6772e5;
  color: white;
  border: none;
  padding: 12px 16px;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.2s ease;
}

button:hover {
  background-color: #5469d4;
}

button:disabled {
  background-color: #b9b9b9;
  cursor: not-allowed;
}

.hidden {
  display: none;
}

pre {
  background-color: #f4f4f4;
  padding: 10px;
  border-radius: 4px;
  overflow-x: auto;
}

 

Step 6: Implement Frontend JavaScript

 

Add the client-side JavaScript in public/script.js:


document.addEventListener('DOMContentLoaded', async () => {
  // Fetch the publishable key from our server
  const response = await fetch('/config');
  const { publishableKey } = await response.json();
  
  // Initialize Stripe.js
  const stripe = Stripe(publishableKey);
  
  // Create an instance of Elements
  const elements = stripe.elements();
  
  // Create the Card Element and mount it
  const cardElement = elements.create('card');
  cardElement.mount('#card-element');
  
  // Handle real-time validation errors from the card Element
  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');
  const submitButton = document.getElementById('submit-button');
  
  form.addEventListener('submit', async (event) => {
    event.preventDefault();
    
    // Disable the submit button to prevent repeated clicks
    submitButton.disabled = true;
    submitButton.textContent = 'Processing...';
    
    try {
      // Create a PaymentMethod
      const { paymentMethod, error } = await stripe.createPaymentMethod({
        type: 'card',
        card: cardElement,
      });
      
      if (error) {
        // Show error to your customer
        const errorElement = document.getElementById('card-errors');
        errorElement.textContent = error.message;
        submitButton.disabled = false;
        submitButton.textContent = 'Pay $9.99';
        return;
      }
      
      // Send the PaymentMethod ID to your server
      const result = await fetch('/create-payment-intent', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          paymentMethodId: paymentMethod.id,
          amount: 999, // $9.99 in cents
        }),
      });
      
      const paymentData = await result.json();
      
      // Handle the result
      const paymentResult = document.getElementById('payment-result');
      const paymentResultMessage = document.getElementById('payment-result-message');
      
      paymentResult.classList.remove('hidden');
      paymentResultMessage.textContent = JSON.stringify(paymentData, null, 2);
      
      // If the payment requires additional action, handle it
      if (paymentData.requiresAction) {
        const { error, paymentIntent } = await stripe.handleCardAction(
          paymentData.clientSecret
        );
        
        if (error) {
          paymentResultMessage.textContent = 'Payment failed: ' + error.message;
        } else {
          // The card action has been handled
          // Confirm the payment with the server
          const confirmResult = await fetch('/confirm-payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              paymentIntentId: paymentIntent.id,
            }),
          });
          
          const confirmData = await confirmResult.json();
          paymentResultMessage.textContent = JSON.stringify(confirmData, null, 2);
        }
      }
    } catch (err) {
      console.error('Error:', err);
      const errorElement = document.getElementById('card-errors');
      errorElement.textContent = 'An unexpected error occurred.';
    } finally {
      // Re-enable the submit button
      submitButton.disabled = false;
      submitButton.textContent = 'Pay $9.99';
    }
  });
});

 

Step 7: Implement Backend Routes

 

Add the following routes to your server.js file:


// Route to expose the publishable key to the frontend
app.get('/config', (req, res) => {
  res.json({
    publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
  });
});

// Route to create a PaymentIntent
app.post('/create-payment-intent', async (req, res) => {
  try {
    const { paymentMethodId, amount } = req.body;
    
    // Create a PaymentIntent with the order amount and currency
    const paymentIntent = await stripe.paymentIntents.create({
      amount,
      currency: 'usd',
      payment\_method: paymentMethodId,
      confirmation\_method: 'manual',
      confirm: true,
      // Verify your integration by passing this to 'automatic' (default) for your first implementation.
      // After that, you can remove this parameter.
      setup_future_usage: 'off\_session',
    });
    
    // Handle the response based on the PaymentIntent status
    if (
      paymentIntent.status === 'requires\_action' &&
      paymentIntent.next_action.type === 'use_stripe\_sdk'
    ) {
      // Tell the client to handle the action
      res.json({
        requiresAction: true,
        clientSecret: paymentIntent.client\_secret,
      });
    } else if (paymentIntent.status === 'succeeded') {
      // The payment didn't need any additional actions and is completed!
      res.json({
        success: true,
        paymentIntent: {
          id: paymentIntent.id,
          status: paymentIntent.status,
        },
      });
    } else {
      // Invalid status
      res.json({
        error: 'Invalid PaymentIntent status',
      });
    }
  } catch (err) {
    console.error('Error creating payment intent:', err);
    res.status(500).json({ error: err.message });
  }
});

// Route to confirm a payment after handling additional actions
app.post('/confirm-payment', async (req, res) => {
  try {
    const { paymentIntentId } = req.body;
    
    const paymentIntent = await stripe.paymentIntents.confirm(paymentIntentId);
    
    if (paymentIntent.status === 'succeeded') {
      res.json({
        success: true,
        paymentIntent: {
          id: paymentIntent.id,
          status: paymentIntent.status,
        },
      });
    } else {
      res.json({
        error: 'Payment confirmation failed',
        paymentIntent: {
          id: paymentIntent.id,
          status: paymentIntent.status,
        },
      });
    }
  } catch (err) {
    console.error('Error confirming payment:', err);
    res.status(500).json({ error: err.message });
  }
});

 

Step 8: Run Your Application

 

Start your Node.js server:


node server.js

Open your browser and navigate to http://localhost:3000 to test your Stripe integration.

For testing, you can use Stripe's test card numbers:

  • 4242 4242 4242 4242 - Successful payment
  • 4000 0025 0000 3155 - Requires authentication (3D Secure)
  • 4000 0000 0000 9995 - Declined payment

 

Step 9: Implement Webhooks (Advanced)

 

For production applications, you should implement Stripe Webhooks to reliably handle asynchronous events:


// Add this to your server.js file
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  
  let event;
  
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    console.error(`Webhook Error: ${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(`PaymentIntent for ${paymentIntent.amount} was successful!`);
      // Then define and call a function to handle the event payment\_intent.succeeded
      break;
    case 'payment_intent.payment_failed':
      const failedPaymentIntent = event.data.object;
      console.log(`Payment failed: ${failedPaymentIntent.last_payment_error?.message}`);
      // Then define and call a function to handle the failed payment
      break;
    // Handle other event types as needed
    default:
      console.log(`Unhandled event type ${event.type}`);
  }
  
  // Return a 200 response to acknowledge receipt of the event
  res.send();
});

Note: To use webhooks locally, you'll need to use the Stripe CLI or a service like ngrok to forward webhook events to your local server.

 

Step 10: Implement Subscription Billing (Advanced)

 

To implement subscription billing with Stripe, you can add the following code:


// Create a subscription plan
app.post('/create-subscription', async (req, res) => {
  try {
    const { paymentMethodId, customerId, priceId } = req.body;
    
    // If no customer ID is provided, create a new customer
    let customer;
    if (!customerId) {
      customer = await stripe.customers.create({
        payment\_method: paymentMethodId,
        email: req.body.email,
        invoice\_settings: {
          default_payment_method: paymentMethodId,
        },
      });
    } else {
      // Attach the payment method to the existing customer
      await stripe.paymentMethods.attach(paymentMethodId, {
        customer: customerId,
      });
      
      // Set it as the default payment method
      await stripe.customers.update(customerId, {
        invoice\_settings: {
          default_payment_method: paymentMethodId,
        },
      });
      
      customer = await stripe.customers.retrieve(customerId);
    }
    
    // Create the subscription
    const subscription = await stripe.subscriptions.create({
      customer: customer.id,
      items: [{ price: priceId }],
      expand: ['latest_invoice.payment_intent'],
    });
    
    res.json({
      subscriptionId: subscription.id,
      clientSecret: subscription.latest_invoice.payment_intent.client\_secret,
      customerId: customer.id,
    });
  } catch (error) {
    console.error('Error creating subscription:', error);
    res.status(500).json({ error: error.message });
  }
});

// Cancel a subscription
app.post('/cancel-subscription', async (req, res) => {
  try {
    const { subscriptionId } = req.body;
    
    const subscription = await stripe.subscriptions.update(subscriptionId, {
      cancel_at_period\_end: true,
    });
    
    res.json({
      success: true,
      subscription: {
        id: subscription.id,
        status: subscription.status,
        cancel_at_period_end: subscription.cancel_at_period_end,
        current_period_end: subscription.current_period_end,
      },
    });
  } catch (error) {
    console.error('Error cancelling subscription:', error);
    res.status(500).json({ error: error.message });
  }
});

 

Step 11: Implement Customer Management (Advanced)

 

To manage customers in Stripe, add these routes:


// Create a customer
app.post('/create-customer', async (req, res) => {
  try {
    const { email, name, paymentMethodId } = req.body;
    
    const customer = await stripe.customers.create({
      email,
      name,
      payment\_method: paymentMethodId,
      invoice\_settings: {
        default_payment_method: paymentMethodId,
      },
    });
    
    res.json({
      success: true,
      customer: {
        id: customer.id,
        email: customer.email,
        name: customer.name,
      },
    });
  } catch (error) {
    console.error('Error creating customer:', error);
    res.status(500).json({ error: error.message });
  }
});

// Get customer details
app.get('/customers/:id', async (req, res) => {
  try {
    const { id } = req.params;
    
    const customer = await stripe.customers.retrieve(id, {
      expand: ['subscriptions', 'payment\_methods'],
    });
    
    res.json({
      success: true,
      customer,
    });
  } catch (error) {
    console.error('Error retrieving customer:', error);
    res.status(500).json({ error: error.message });
  }
});

// Update customer
app.post('/customers/:id', async (req, res) => {
  try {
    const { id } = req.params;
    const { email, name } = req.body;
    
    const customer = await stripe.customers.update(id, {
      email,
      name,
    });
    
    res.json({
      success: true,
      customer: {
        id: customer.id,
        email: customer.email,
        name: customer.name,
      },
    });
  } catch (error) {
    console.error('Error updating customer:', error);
    res.status(500).json({ error: error.message });
  }
});

 

Step 12: Error Handling and Best Practices

 

Enhance your application with proper error handling:


// Middleware for better error handling
app.use((err, req, res, next) => {
  console.error('Application error:', err);
  
  if (err.type === 'StripeCardError') {
    // Card errors, such as declined card
    return res.status(400).json({
      error: {
        message: err.message,
        code: err.code,
        type: err.type,
      }
    });
  } else if (err.type === 'StripeInvalidRequestError') {
    // Invalid parameters were supplied to Stripe's API
    return res.status(400).json({
      error: {
        message: 'Invalid parameters provided to the Stripe API',
        code: err.code,
        type: err.type,
      }
    });
  } else if (err.type === 'StripeAPIError') {
    // An error occurred internally with Stripe's API
    return res.status(500).json({
      error: {
        message: 'An error occurred with Stripe',
        code: err.code,
        type: err.type,
      }
    });
  } else if (err.type === 'StripeConnectionError') {
    // Some kind of error occurred during the HTTPS communication
    return res.status(503).json({
      error: {
        message: 'Could not connect to Stripe servers',
        code: err.code,
        type: err.type,
      }
    });
  } else if (err.type === 'StripeAuthenticationError') {
    // You probably used an incorrect API key
    return res.status(401).json({
      error: {
        message: 'Authentication with Stripe failed',
        code: err.code,
        type: err.type,
      }
    });
  } else {
    // Generic error handling
    return res.status(500).json({
      error: {
        message: 'An unexpected error occurred',
      }
    });
  }
});

 

Conclusion

 

Congratulations! You've successfully integrated Stripe's payment processing API with your Node.js application. You've learned how to:

  • Set up your Stripe account and get API keys
  • Create a basic Node.js Express server
  • Implement a client-side payment form with Stripe Elements
  • Process one-time payments with PaymentIntents
  • Implement webhooks for handling asynchronous events
  • Set up subscription billing
  • Manage customers in Stripe
  • Implement proper error handling

This tutorial covers the basics, but Stripe offers many more features such as invoicing, connect platform, tax calculation, and more. Refer to the Stripe API documentation for more advanced features and use cases.

Want to explore opportunities to work with us?

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

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev 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.

CPO, Praction - Arkady Sokolov

May 2, 2023

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!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev 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.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-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.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

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!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022