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.
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 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:
// 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:
// 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
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.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.