/stripe-guides

How to store card details securely with Stripe?

Learn how to securely store and manage card details with Stripe, minimize PCI compliance, and implement best practices for safe recurring payments and seamless checkout.

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 store card details securely with Stripe?

How to Store Card Details Securely with Stripe

 

Introduction

 

Storing payment card details securely is critical for businesses that process recurring payments or want to offer a seamless checkout experience. Stripe provides secure methods to store card information without exposing your systems to PCI compliance requirements. This tutorial will guide you through the process of securely storing and managing payment card details using Stripe.

 

Step 1: Set Up Your Stripe Account

 

Before you begin storing card details, you need to set up your Stripe account:

  1. Sign up for a Stripe account at https://stripe.com/
  2. Verify your email address and complete the registration process
  3. Navigate to the Developers section to access your API keys
  4. Note down your publishable key and secret key (Keep your secret key confidential)

// Example of where you'll use these keys
const stripe = require('stripe')('sk_test_YourSecretKeyHere');

 

Step 2: Install Stripe Libraries

 

Depending on your technology stack, install the appropriate Stripe library:

For Node.js:


npm install stripe

For Python:


pip install stripe

For PHP:


composer require stripe/stripe-php

For Ruby:


gem install stripe

 

Step 3: Implement Stripe Elements for Card Collection

 

Stripe Elements provides pre-built UI components for collecting card information securely:

  1. Add the Stripe.js script to your payment page:


  1. Create a card element container in your HTML:

  1. Initialize Stripe Elements in your JavaScript:

// Initialize Stripe
const stripe = Stripe('pk_test_YourPublishableKeyHere');
const elements = stripe.elements();

// Create a card Element and mount it
const cardElement = elements.create('card');
cardElement.mount('#card-element');

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

 

Step 4: Create a Setup Intent on Your Server

 

A SetupIntent is Stripe's way of preparing to collect payment method details for future use:


// Node.js example
const stripe = require('stripe')('sk_test_YourSecretKeyHere');

app.post('/create-setup-intent', async (req, res) => {
  try {
    // Create a SetupIntent
    const setupIntent = await stripe.setupIntents.create({
      customer: 'cus\_ExistingCustomerIdOrCreateOne',
      payment_method_types: ['card'],
    });
    
    // Send the client secret to the client
    res.json({ clientSecret: setupIntent.client\_secret });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

 

Step 5: Confirm the Setup Intent on the Client

 

Use the client secret to complete the setup process and store the card:


// Add this to your client-side JavaScript
const form = document.getElementById('payment-form');

form.addEventListener('submit', async (event) => {
  event.preventDefault();
  
  // Disable the submit button to prevent repeated clicks
  document.querySelector('button').disabled = true;
  
  // Fetch the SetupIntent client secret from the server
  const response = await fetch('/create-setup-intent', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      // You can pass additional customer data here if needed
    }),
  });
  
  const data = await response.json();
  
  // Complete the setup
  const result = await stripe.confirmCardSetup(data.clientSecret, {
    payment\_method: {
      card: cardElement,
      billing\_details: {
        name: 'Jenny Rosen', // Get this from your form
        email: '[email protected]', // Get this from your form
      },
    },
  });
  
  if (result.error) {
    // Display error to your customer
    const errorElement = document.getElementById('card-errors');
    errorElement.textContent = result.error.message;
    document.querySelector('button').disabled = false;
  } else {
    // The setup has succeeded. The card is now stored.
    // You can send the payment\_method ID to your server for later use
    handleSetupSuccess(result.setupIntent.payment\_method);
  }
});

function handleSetupSuccess(paymentMethodId) {
  // Send the payment method ID to your server
  fetch('/save-payment-method', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ paymentMethodId }),
  })
  .then(response => response.json())
  .then(data => {
    // Handle success, perhaps redirect or show a success message
    window.location.href = '/payment-success';
  })
  .catch(error => {
    console.error('Error:', error);
  });
}

 

Step 6: Store the Payment Method on Your Server

 

When you receive the payment method ID, attach it to the customer:


// Node.js example
app.post('/save-payment-method', async (req, res) => {
  const { paymentMethodId } = req.body;
  const customerId = 'cus\_ExistingCustomerId'; // Get this from your database
  
  try {
    // Attach the payment method to the customer
    await stripe.paymentMethods.attach(paymentMethodId, {
      customer: customerId,
    });
    
    // Optionally set this as the default payment method
    await stripe.customers.update(customerId, {
      invoice\_settings: {
        default_payment_method: paymentMethodId,
      },
    });
    
    res.json({ success: true });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

 

Step 7: Retrieve Stored Payment Methods

 

To display stored cards to customers, retrieve their payment methods:


// Node.js example
app.get('/get-payment-methods', async (req, res) => {
  const customerId = 'cus\_ExistingCustomerId'; // Get this from your database or session
  
  try {
    const paymentMethods = await stripe.paymentMethods.list({
      customer: customerId,
      type: 'card',
    });
    
    res.json(paymentMethods.data);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

 

Step 8: Use Stored Cards for Payments

 

When you need to charge a stored card, create a payment intent using the stored payment method:


// Node.js example
app.post('/create-payment', async (req, res) => {
  const { amount, paymentMethodId } = req.body;
  const customerId = 'cus\_ExistingCustomerId'; // Get this from your database
  
  try {
    // Create a PaymentIntent
    const paymentIntent = await stripe.paymentIntents.create({
      amount, // Amount in cents
      currency: 'usd',
      customer: customerId,
      payment\_method: paymentMethodId,
      off\_session: true, // Important for using stored cards
      confirm: true, // Confirm the payment immediately
    });
    
    res.json({ success: true, paymentIntentId: paymentIntent.id });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

 

Step 9: Implement Card Management Features

 

Allow users to delete saved cards:


// Node.js example
app.post('/delete-payment-method', async (req, res) => {
  const { paymentMethodId } = req.body;
  
  try {
    // Detach the payment method
    await stripe.paymentMethods.detach(paymentMethodId);
    res.json({ success: true });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Client-side JavaScript to display and delete cards:


// Fetch and display saved cards
async function displaySavedCards() {
  const response = await fetch('/get-payment-methods');
  const paymentMethods = await response.json();
  
  const cardList = document.getElementById('saved-cards');
  cardList.innerHTML = '';
  
  paymentMethods.forEach(method => {
    const card = method.card;
    const div = document.createElement('div');
    div.className = 'saved-card';
    div.innerHTML = \`
      
${card.brand.toUpperCase()} •••• •••• •••• ${card.last4} Expires ${card.exp_month}/${card.exp_year}
\`; cardList.appendChild(div); }); // Add event listeners to delete buttons document.querySelectorAll('.delete-card').forEach(button => { button.addEventListener('click', async (e) => { const paymentMethodId = e.target.dataset.id; await deleteCard(paymentMethodId); displaySavedCards(); // Refresh the list }); }); } // Function to delete a card async function deleteCard(paymentMethodId) { try { const response = await fetch('/delete-payment-method', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ paymentMethodId }), }); const result = await response.json(); if (result.success) { // Show success message alert('Card deleted successfully'); } } catch (error) { console.error('Error deleting card:', error); } } // Call this function when the page loads document.addEventListener('DOMContentLoaded', displaySavedCards);

 

Step 10: Implement Webhook Handling for Payment Events

 

Set up webhooks to handle payment events like successful charges or failures:


// Node.js example
const endpointSecret = 'whsec\_YourWebhookSigningSecret';

app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['stripe-signature'];
  
  let event;
  
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  
  // Handle the event
  switch (event.type) {
    case 'payment\_intent.succeeded':
      const paymentIntent = event.data.object;
      handleSuccessfulPayment(paymentIntent);
      break;
    case 'payment_intent.payment_failed':
      const failedPayment = event.data.object;
      handleFailedPayment(failedPayment);
      break;
    case 'payment\_method.attached':
      const paymentMethod = event.data.object;
      console.log('PaymentMethod was attached to a Customer!');
      break;
    // ... handle other event types
    default:
      console.log(`Unhandled event type ${event.type}`);
  }
  
  // Return a 200 response to acknowledge receipt of the event
  res.send();
});

function handleSuccessfulPayment(paymentIntent) {
  // Update your database
  // Send confirmation email, etc.
  console.log(`PaymentIntent ${paymentIntent.id} was successful!`);
}

function handleFailedPayment(paymentIntent) {
  // Notify the customer
  // Update your database
  console.log(`PaymentIntent ${paymentIntent.id} failed: ${paymentIntent.last_payment_error?.message}`);
}

 

Security Best Practices

 

  • Never store raw card data on your servers; always use Stripe's infrastructure.
  • Always use HTTPS for all communications involving payment data.
  • Implement proper authentication and authorization for accessing payment methods.
  • Keep your Stripe API keys secure and never expose them in client-side code.
  • Regularly rotate your webhook signing secrets.
  • Implement rate limiting on your payment endpoints to prevent abuse.
  • Log all payment-related activities for audit purposes, but never log full card details.

 

Conclusion

 

By following this guide, you've implemented a secure system for storing and using payment card details through Stripe. This approach keeps you out of PCI compliance scope while providing a seamless payment experience for your customers. Remember to thoroughly test your implementation in Stripe's test environment before moving to production.

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