Learn how to accept one-time and recurring donations on your website using the Stripe API, with step-by-step setup, code examples, security tips, and dashboard integration.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Step 1: Set Up Your Stripe Account
Before integrating Stripe into your website, you need to create and set up a Stripe account:
Step 2: Install Stripe Libraries
You'll need to install the Stripe libraries for your project. Here's how to do it in different environments:
For Node.js projects:
npm install stripe
For PHP projects:
composer require stripe/stripe-php
Step 3: Include Stripe.js in Your Frontend
Add the Stripe.js library to your HTML file:
<!DOCTYPE html>
<html>
<head>
<title>Donation Form</title>
<script src="https://js.stripe.com/v3/"></script>
<style>
.donation-form {
max-width: 500px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.form-row {
margin-bottom: 15px;
}
button {
background: #635BFF;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
#card-element {
border: 1px solid #ddd;
padding: 10px;
border-radius: 4px;
}
#card-errors {
color: #fa755a;
margin-top: 10px;
}
</style>
</head>
<body>
<!-- We'll add the form here in the next step -->
</body>
</html>
Step 4: Create Your Donation Form
Add the donation form to your HTML body:
<div class="donation-form">
<h2>Support Our Cause</h2>
<form id="payment-form">
<div class="form-row">
<label for="name">Name</label>
<input type="text" id="name" placeholder="John Doe" required />
</div>
<div class="form-row">
<label for="email">Email</label>
<input type="email" id="email" placeholder="[email protected]" required />
</div>
<div class="form-row">
<label for="amount">Donation Amount ($)</label>
<input type="number" id="amount" min="1" step="1" value="10" required />
</div>
<div class="form-row">
<label for="card-element">Credit or Debit Card</label>
<div id="card-element">
<!-- Stripe Card Element will be inserted here -->
</div>
<div id="card-errors" role="alert"></div>
</div>
<button type="submit">Donate Now</button>
</form>
</div>
Step 5: Initialize Stripe Elements
Add JavaScript to initialize Stripe Elements:
<script>
// Initialize Stripe with your publishable key
const stripe = Stripe('pk_test_your_publishable_key');
// Create an instance of Elements
const elements = stripe.elements();
// Create and mount the Card Element
const cardElement = elements.create('card');
cardElement.mount('#card-element');
// Handle real-time validation errors from the card Element
cardElement.on('change', function(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();
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
const amount = document.getElementById('amount').value;
try {
// Create a payment method using the card element
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing\_details: {
name: name,
email: email
}
});
if (error) {
// Show error to your customer
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
return;
}
// Send payment method ID to your server
const response = await fetch('/process-donation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
payment_method_id: paymentMethod.id,
name: name,
email: email,
amount: amount
})
});
const result = await response.json();
// Handle server response
if (result.error) {
// Show error to your customer
const errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else if (result.requires\_action) {
// Use Stripe.js to handle required card action
const { error: actionError } = await stripe.handleCardAction(
result.payment_intent_client\_secret
);
if (actionError) {
// Show error to your customer
const errorElement = document.getElementById('card-errors');
errorElement.textContent = actionError.message;
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
const serverResponse = await fetch('/process-donation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
payment_intent_id: result.payment_intent_id
})
});
const serverResult = await serverResponse.json();
if (serverResult.error) {
// Show error to your customer
const errorElement = document.getElementById('card-errors');
errorElement.textContent = serverResult.error.message;
} else {
// Show a success message to your customer
alert('Thank you for your donation!');
form.reset();
}
}
} else {
// Show a success message to your customer
alert('Thank you for your donation!');
form.reset();
}
} catch (error) {
console.error('Error:', error);
const errorElement = document.getElementById('card-errors');
errorElement.textContent = 'An unexpected error occurred. Please try again later.';
}
});
</script>
Step 6: Create Server-Side Endpoint (Node.js Example)
Create a server-side endpoint to process the donation using Node.js and Express:
const express = require('express');
const bodyParser = require('body-parser');
const stripe = require('stripe')('sk_test_your_secret_key');
const app = express();
app.use(bodyParser.json());
app.post('/process-donation', async (req, res) => {
try {
const { payment_method_id, payment_intent_id, name, email, amount } = req.body;
// If payment_intent_id is passed, confirm the payment
if (payment_intent_id) {
const intent = await stripe.paymentIntents.confirm(payment_intent_id);
return res.json({
success: true,
payment: intent
});
}
// Create a new PaymentIntent
const paymentIntent = await stripe.paymentIntents.create({
payment_method: payment_method\_id,
amount: amount \* 100, // Stripe expects amount in cents
currency: 'usd',
confirmation\_method: 'manual',
confirm: true,
description: 'Donation',
receipt\_email: email,
metadata: {
donor\_name: name,
donor\_email: email
}
});
// Handle payment status
if (
paymentIntent.status === 'requires\_action' &&
paymentIntent.next_action.type === 'use_stripe\_sdk'
) {
// Tell the client to handle the action
return res.json({
requires\_action: true,
payment_intent_client_secret: paymentIntent.client_secret,
payment_intent_id: paymentIntent.id
});
} else if (paymentIntent.status === 'succeeded') {
// The payment didn't need any additional actions and completed!
return res.json({
success: true,
payment: paymentIntent
});
} else {
// Invalid status
return res.json({
error: {
message: 'Invalid PaymentIntent status'
}
});
}
} catch (error) {
return res.json({
error: {
message: error.message
}
});
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Step 7: Create Server-Side Endpoint (PHP Example)
Here's an alternative server-side implementation using PHP:
confirm();
echo json\_encode(['success' => true, 'payment' => $intent]);
} else {
// Create a new PaymentIntent
$paymentIntent = \Stripe\PaymentIntent::create([
'payment_method' => $body['payment_method\_id'],
'amount' => $body['amount'] \* 100, // Stripe expects amount in cents
'currency' => 'usd',
'confirmation\_method' => 'manual',
'confirm' => true,
'description' => 'Donation',
'receipt\_email' => $body['email'],
'metadata' => [
'donor\_name' => $body['name'],
'donor\_email' => $body['email']
]
]);
if ($paymentIntent->status == 'requires\_action' &&
$paymentIntent->next_action->type == 'use_stripe\_sdk') {
// Tell the client to handle the action
echo json\_encode([
'requires\_action' => true,
'payment_intent_client_secret' => $paymentIntent->client_secret,
'payment_intent_id' => $paymentIntent->id
]);
} else if ($paymentIntent->status == 'succeeded') {
// The payment didn't need any additional actions and completed!
echo json\_encode([
'success' => true,
'payment' => $paymentIntent
]);
} else {
// Invalid status
echo json\_encode([
'error' => [
'message' => 'Invalid PaymentIntent status'
]
]);
}
}
} catch (\Stripe\Exception\ApiErrorException $e) {
echo json\_encode([
'error' => [
'message' => $e->getMessage()
]
]);
}
?>
Step 8: Test Your Donation System
Before going live, thoroughly test your donation system:
Step 9: Implement Donation Success Page
Create a success page to redirect users after successful donations:
<!DOCTYPE html>
<html>
<head>
<title>Donation Successful</title>
<style>
.success-container {
max-width: 600px;
margin: 50px auto;
text-align: center;
padding: 30px;
border: 1px solid #e0e0e0;
border-radius: 5px;
}
.success-icon {
color: #4CAF50;
font-size: 60px;
margin-bottom: 20px;
}
.donation-details {
margin-top: 30px;
text-align: left;
padding: 20px;
background-color: #f9f9f9;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="success-container">
<div class="success-icon">✓</div>
<h1>Thank You for Your Donation!</h1>
<p>Your contribution helps us continue our important work.</p>
<div class="donation-details">
<h3>Donation Details</h3>
<p><strong>Amount:</strong> $<span id="donation-amount"></span></p>
<p><strong>Date:</strong> <span id="donation-date"></span></p>
<p><strong>Transaction ID:</strong> <span id="transaction-id"></span></p>
</div>
<p>A receipt has been sent to your email.</p>
<a href="index.html">Return to Homepage</a>
</div>
<script>
// Get URL parameters to display donation details
const urlParams = new URLSearchParams(window.location.search);
document.getElementById('donation-amount').textContent = urlParams.get('amount') || 'N/A';
document.getElementById('transaction-id').textContent = urlParams.get('transaction\_id') || 'N/A';
document.getElementById('donation-date').textContent = new Date().toLocaleDateString();
</script>
</body>
</html>
Step 10: Add Webhook Support for Asynchronous Events
Set up webhooks to handle asynchronous events like successful payments or disputes:
// Node.js webhook implementation
const express = require('express');
const bodyParser = require('body-parser');
const stripe = require('stripe')('sk_test_your_secret_key');
const app = express();
// Use JSON parser for webhook events
app.post('/webhook', bodyParser.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
// Verify the event came from Stripe using your webhook secret
event = stripe.webhooks.constructEvent(
req.body,
sig,
'whsec_your_webhook\_secret'
);
} 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 ${paymentIntent.id} succeeded`);
// Update your database, send thank-you emails, etc.
// Example: Record donation in database
await recordDonationInDatabase({
payment\_id: paymentIntent.id,
amount: paymentIntent.amount / 100, // Convert back from cents
email: paymentIntent.receipt\_email,
name: paymentIntent.metadata.donor\_name,
status: 'succeeded',
date: new Date()
});
break;
case 'charge.refunded':
const charge = event.data.object;
console.log(`Charge ${charge.id} was refunded`);
// Handle refund logic
break;
case 'payment_intent.payment_failed':
const failedPayment = event.data.object;
console.log(`Payment failed: ${failedPayment.id}`);
// Handle failed payment
break;
default:
// Unexpected event type
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
res.json({received: true});
});
// Example function to record donation in database
async function recordDonationInDatabase(donationData) {
// In a real implementation, you would save to your database
console.log('Donation recorded:', donationData);
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
Step 11: Implement Recurring Donations
To implement recurring donations, use Stripe Subscriptions:
// Frontend JavaScript additions for recurring donations
// Add this to your form HTML first:
/\*
<div class="form-row">
<label>Donation Frequency</label>
<select id="donation-frequency">
<option value="one-time">One-time</option>
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
</select>
</div>
\*/
// In your form submission handler, modify to handle subscriptions:
const handleSubmit = async (event) => {
event.preventDefault();
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
const amount = document.getElementById('amount').value;
const frequency = document.getElementById('donation-frequency').value;
try {
// Create a payment method
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing\_details: {
name: name,
email: email
}
});
if (error) {
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
return;
}
// Send to server
const response = await fetch('/process-donation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
payment_method_id: paymentMethod.id,
name: name,
email: email,
amount: amount,
frequency: frequency // Add the frequency parameter
})
});
// Handle response as before
// ...
} catch (error) {
console.error('Error:', error);
}
};
Server-side implementation for subscriptions:
// Node.js endpoint for handling both one-time and recurring donations
app.post('/process-donation', async (req, res) => {
try {
const { payment_method_id, name, email, amount, frequency } = req.body;
// Handle based on donation frequency
if (frequency === 'one-time') {
// Process one-time donation (as shown in previous steps)
const paymentIntent = await stripe.paymentIntents.create({
payment_method: payment_method\_id,
amount: amount \* 100,
currency: 'usd',
confirmation\_method: 'manual',
confirm: true,
description: 'One-time Donation',
receipt\_email: email,
metadata: {
donor\_name: name,
donor\_email: email
}
});
// Handle payment intent status...
} else {
// Process recurring donation (subscription)
// First, create a customer
const customer = await stripe.customers.create({
payment_method: payment_method\_id,
email: email,
name: name,
invoice\_settings: {
default_payment_method: payment_method_id,
},
});
// Determine the interval based on frequency
const interval = frequency === 'monthly' ? 'month' : 'year';
// Create or retrieve a product for the donation
const product = await stripe.products.create({
name: `${frequency.charAt(0).toUpperCase() + frequency.slice(1)} Donation`,
});
// Create a price for the subscription
const price = await stripe.prices.create({
product: product.id,
unit\_amount: amount \* 100,
currency: 'usd',
recurring: {
interval: interval,
},
metadata: {
type: 'donation'
}
});
// Create the subscription
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: price.id }],
expand: ['latest_invoice.payment_intent'],
metadata: {
donor\_name: name,
donor\_email: email
}
});
// Check if further authentication is needed
const status = subscription.latest_invoice.payment_intent.status;
const client_secret = subscription.latest_invoice.payment_intent.client_secret;
if (status === 'requires\_action') {
// Handle 3D Secure authentication
return res.json({
requires\_action: true,
payment_intent_client_secret: client_secret,
subscription\_id: subscription.id
});
} else {
// Subscription created successfully
return res.json({
success: true,
subscription: subscription
});
}
}
} catch (error) {
return res.json({
error: {
message: error.message
}
});
}
});
Step 12: Add Donation Analytics
Implement analytics to track and analyze donation metrics:
// Node.js example for donation analytics
const express = require('express');
const stripe = require('stripe')('sk_test_your_secret_key');
const { Pool } = require('pg'); // Using PostgreSQL for this example
// Database connection
const pool = new Pool({
user: 'your\_user',
host: 'localhost',
database: 'donations\_db',
password: 'your\_password',
port: 5432,
});
const app = express();
// API endpoint to get donation analytics
app.get('/donation-analytics', async (req, res) => {
try {
// Total amount donated
const totalResult = await pool.query(
'SELECT SUM(amount) as total FROM donations WHERE status = $1',
['succeeded']
);
// Donations by month
const monthlyResult = await pool.query(\`
SELECT
DATE_TRUNC('month', donation_date) as month,
SUM(amount) as monthly\_total,
COUNT(\*) as donation\_count
FROM donations
WHERE status = $1
GROUP BY DATE_TRUNC('month', donation_date)
ORDER BY month DESC
LIMIT 12
\`, ['succeeded']);
// New vs returning donors
const donorTypeResult = await pool.query(\`
SELECT
COUNT(DISTINCT donor_email) as unique_donors,
SUM(CASE WHEN donation_count = 1 THEN 1 ELSE 0 END) as new_donors,
SUM(CASE WHEN donation_count > 1 THEN 1 ELSE 0 END) as returning_donors
FROM (
SELECT
donor\_email,
COUNT(\*) as donation\_count
FROM donations
WHERE status = $1
GROUP BY donor\_email
) as donor\_counts
\`, ['succeeded']);
// Average donation amount
const avgResult = await pool.query(
'SELECT AVG(amount) as average FROM donations WHERE status = $1',
['succeeded']
);
// Return combined analytics
res.json({
total\_donated: totalResult.rows[0].total || 0,
monthly\_stats: monthlyResult.rows,
donor\_stats: donorTypeResult.rows[0],
average\_donation: avgResult.rows[0].average || 0
});
} catch (error) {
console.error('Analytics error:', error);
res.status(500).json({ error: 'Failed to retrieve donation analytics' });
}
});
app.listen(3000, () => {
console.log('Analytics server running on port 3000');
});
Step 13: Add a Donation Management Dashboard
Create a simple dashboard to manage donations:
<!DOCTYPE html>
<html>
<head>
<title>Donation Management</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.dashboard {
display: flex;
min-height: 100vh;
}
.sidebar {
width: 250px;
background-color: #2c3e50;
color: white;
padding: 20px;
}
.sidebar ul {
list-style-type: none;
padding: 0;
}
.sidebar li {
margin-bottom: 15px;
}
.sidebar a {
color: white;
text-decoration: none;
}
.main-content {
flex: 1;
padding: 20px;
}
.stat-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
padding: 20px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
margin: 10px 0;
}
.chart-container {
margin-bottom: 30px;
height: 300px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px 15px;
border-bottom: 1px solid #ddd;
text-align: left;
}
th {
background-color: #f2f2f2;
}
</style>
</head>
<body>
<div class="dashboard">
<div class="sidebar">
<h2>Donation Admin</h2>
<ul>
<li><a href="#" class="active">Dashboard</a></li>
<li><a href="#">Donations</a></li>
<li><a href="#">Donors</a></li>
<li><a href="#">Reports</a></li>
<li><a href="#">Settings</a></li>
</ul>
</div>
<div class="main-content">
<h1>Donation Dashboard</h1>
<div class="stat-cards">
<div class="stat-card">
<div>Total Donations</div>
<div class="stat-value" id="total-donations">$0</div>
<div>All time</div>
</div>
<div class="stat-card">
<div>Donations This Month</div>
<div class="stat-value" id="monthly-donations">$0</div>
<div>vs previous month</div>
</div>
<div class="stat-card">
<div>Average Donation</div>
<div class="stat-value" id="avg-donation">$0</div>
<div>Per transaction</div>
</div>
<div class="stat-card">
<div>Total Donors</div>
<div class="stat-value" id="donor-count">0</div>
<div>Unique contributors</div>
</div>
</div>
<div class="chart-container">
<canvas id="donations-chart"></canvas>
</div>
<h2>Recent Donations</h2>
<table id="recent-donations">
<thead>
<tr>
<th>Date</th>
<th>Donor</th>
<th>Amount</th>
<th>Type</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<!-- Donation rows will be added here -->
</tbody>
</table>
</div>
</div>
<script>
// Fetch donation analytics from server
async function fetchDonationData() {
try {
const response = await fetch('/donation-analytics');
const data = await response.json();
// Update dashboard stats
document.getElementById('total-donations').textContent = '$' +
data.total\_donated.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
document.getElementById('avg-donation').textContent = '$' +
data.average\_donation.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
document.getElementById('donor-count').textContent =
data.donor_stats.unique_donors.toLocaleString('en-US');
// Get current month's donations
const currentMonth = data.monthly_stats[0] || {monthly_total: 0};
document.getElementById('monthly-donations').textContent = '$' +
currentMonth.monthly\_total.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
// Create donation chart
createDonationChart(data.monthly\_stats);
// Fetch and populate recent donations
fetchRecentDonations();
} catch (error) {
console.error('Error fetching donation data:', error);
}
}
// Create chart for donation trends
function createDonationChart(monthlyData) {
const ctx = document.getElementById('donations-chart').getContext('2d');
// Prepare data for chart
const months = monthlyData.map(item => {
const date = new Date(item.month);
return date.toLocaleDateString('en-US', {month: 'short', year: 'numeric'});
}).reverse();
const amounts = monthlyData.map(item => item.monthly\_total).reverse();
const counts = monthlyData.map(item => item.donation\_count).reverse();
new Chart(ctx, {
type: 'bar',
data: {
labels: months,
datasets: [
{
label: 'Donation Amount ($)',
data: amounts,
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
yAxisID: 'y'
},
{
label: 'Number of Donations',
data: counts,
type: 'line',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Amount ($)'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
grid: {
drawOnChartArea: false
},
title: {
display: true,
text: 'Count'
}
}
}
}
});
}
// Fetch recent donations
async function fetchRecentDonations() {
try {
const response = await fetch('/recent-donations');
const donations = await response.json();
const tableBody = document.querySelector('#recent-donations tbody');
tableBody.innerHTML = '';
donations.forEach(donation => {
const row = document.createElement('tr');
// Format date
const date = new Date(donation.donation\_date);
const formattedDate = date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
row.innerHTML = \`
<td>${formattedDate}</td>
<td>${donation.donor\_name}</td>
<td>$${donation.amount.toFixed(2)}</td>
<td>${donation.frequency || 'One-time'}</td>
<td>
<span class="status-${donation.status.toLowerCase()}">
${donation.status.charAt(0).toUpperCase() + donation.status.slice(1)}
</span>
</td>
\`;
tableBody.appendChild(row);
});
} catch (error) {
console.error('Error fetching recent donations:', error);
}
}
// Initialize dashboard
fetchDonationData();
</script>
</body>
</html>
Step 14: Implement Security Best Practices
Ensure your donation system is secure:
// Node.js example with security enhancements
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
const stripe = require('stripe')('sk_test_your_secret_key');
const app = express();
// Security middleware
app.use(helmet()); // Sets various HTTP headers for security
app.use(cors({
origin: 'https://yourdomain.com', // Restrict to your domain
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Rate limiting to prevent abuse
const apiLimiter = rateLimit({
windowMs: 15 _ 60 _ 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later.'
});
app.use('/api/', apiLimiter);
// For CSRF protection
app.use(cookieParser());
app.use(csrf({ cookie: true }));
// Input validation middleware example
const validateDonationInput = (req, res, next) => {
const { name, email, amount } = req.body;
// Check for required fields
if (!name || !email || !amount) {
return res.status(400).json({
error: 'Missing required fields'
});
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.status(400).json({
error: 'Invalid email format'
});
}
// Validate amount
const numAmount = parseFloat(amount);
if (isNaN(numAmount) || numAmount <= 0) {
return res.status(400).json({
error: 'Amount must be greater than zero'
});
}
// If all validations pass, proceed
next();
};
// Include CSRF token in forms
app.get('/donation-form', (req, res) => {
// Pass CSRF token to your form
res.render('donation-form', { csrfToken: req.csrfToken() });
});
// Secure donation processing endpoint
app.post('/process-donation', validateDonationInput, async (req, res) => {
try {
// Donation processing logic
// ...
} catch (error) {
console.error('Donation processing error:', error);
// Do not expose detailed error info to client
res.status(500).json({ error: 'An error occurred while processing your donation' });
}
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
if (err.code === 'EBADCSRFTOKEN') {
return res.status(403).json({
error: 'Invalid or expired form submission. Please try again.'
});
}
res.status(500).json({
error: 'Something went wrong on our end. Please try again later.'
});
});
app.listen(3000, () => {
console.log('Secure server running on port 3000');
});
Step 15: Set Up Stripe in Production
When ready to accept real donations, switch to production mode:
Conclusion
You've now implemented a complete donation system using Stripe API. This solution includes one-time and recurring donations, a management dashboard, security best practices, and analytics. Remember to:
Stripe's comprehensive documentation is available at stripe.com/docs for additional details and advanced features.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.