Project Introduction
A comprehensive online donation platform that enables non-profits, charities, churches, and individuals to accept donations seamlessly. This system features one-time and recurring donations, campaign tracking, donor management, tax receipt generation, and detailed reporting. Perfect for building your own fundraising platform similar to GoFundMe or DonorPerfect.
✨ Features
Donor Features
- Easy Donation: Quick one-time or recurring donations
- Multiple Payment Methods: Credit card, PayPal, bank transfer, cryptocurrency
- Donation Tracking: View donation history and receipts
- Recurring Management: Update or cancel recurring donations
- Tax Receipts: Automatic generation of tax-deductible receipts
- Tribute/Memorial Gifts: Donate in honor of someone
- Anonymous Giving: Option to hide donor identity
- Currency Support: Accept donations in multiple currencies
Campaign Features
- Goal Tracking: Visual progress bars and thermometers
- Urgent Appeals: Highlight time-sensitive campaigns
- Matching Gifts: Corporate matching programs
- Peer-to-Peer Fundraising: Supporters create their own fundraising pages
- Campaign Updates: Post updates to donors
- Milestone Alerts: Notify donors when goals are reached
Organization Features
- Campaign Management: Create and manage fundraising campaigns
- Donor Management: Track donor information and history
- Email Communications: Send thank-yous and newsletters
- Export Data: Download donor lists and reports
- Grant Tracking: Monitor foundation and corporate grants
- Volunteer Integration: Connect donations with volunteer hours
Admin Features
- Dashboard Analytics: Real-time donation metrics
- User Management: Manage organizations and donors
- Fee Management: Configure platform fees
- Compliance Tools: 501(c)(3) verification
- Fraud Detection: Monitor suspicious transactions
- Payout Processing: Send funds to organizations
Technical Features
- Recurring Billing: Automated subscription management
- Tax Receipt Generation: PDF receipts with organization details
- Email Automation: Triggered emails for donations
- Webhook Support: Integration with CRM systems
- API Access: Programmatic donation creation
- GDPR Compliance: Data export and deletion tools
📁 File Structure
blog-website/ │ ├── donate/ # Main donation directory │ ├── index.php # Donation landing page │ ├── campaigns.php # Browse campaigns │ ├── campaign.php # Single campaign view │ ├── donate.php # Donation form │ ├── recurring.php # Recurring donation setup │ ├── tribute.php # Tribute/memorial donations │ ├── receipt.php # View/download receipt │ │ │ ├── donor/ # Donor dashboard │ │ ├── dashboard.php # Donor dashboard │ │ ├── history.php # Donation history │ │ ├── recurring.php # Manage recurring │ │ ├── receipts.php # Tax receipts │ │ └── settings.php # Profile settings │ │ │ ├── organization/ # Organization panel │ │ ├── dashboard.php # Organization dashboard │ │ ├── campaigns.php # Manage campaigns │ │ ├── create-campaign.php # Create campaign │ │ ├── edit-campaign.php # Edit campaign │ │ ├── donors.php # Donor management │ │ ├── donations.php # View donations │ │ ├── reports.php # Financial reports │ │ ├── payout.php # Request payout │ │ └── settings.php # Organization settings │ │ │ ├── admin/ # Admin panel │ │ ├── dashboard.php # Admin dashboard │ │ ├── organizations.php # Manage organizations │ │ ├── campaigns.php # Campaign moderation │ │ ├── donors.php # Donor management │ │ ├── transactions.php # Transaction oversight │ │ ├── payouts.php # Process payouts │ │ ├── fees.php # Fee configuration │ │ └── reports.php # Platform reports │ │ │ ├── api/ # REST API endpoints │ │ ├── campaigns.php # Campaigns API │ │ ├── donations.php # Donations API │ │ ├── webhook.php # Payment webhooks │ │ └── receipt.php # Receipt generation │ │ │ ├── includes/ # Core includes │ │ ├── donation-config.php # Configuration │ │ ├── donation-functions.php # Core functions │ │ ├── payment.php # Payment processing │ │ ├── receipt-generator.php # PDF receipt generation │ │ ├── email.php # Email templates │ │ └── recurring-processor.php # Recurring billing │ │ │ ├── css/ # Stylesheets │ │ ├── donate.css # Main styles │ │ └── campaign.css # Campaign styles │ │ │ ├── js/ # JavaScript │ │ ├── donate.js # Donation form handling │ │ ├── campaign.js # Campaign page interactions │ │ └── organization.js # Organization dashboard │ │ │ └── uploads/ # Uploads │ ├── organizations/ # Organization logos │ ├── campaigns/ # Campaign images │ └── receipts/ # Generated receipts │ ├── .env # Environment variables ├── composer.json # Composer dependencies │ └── database/ └── donations.sql # Database schema
🗄️ Database Schema (database/donations.sql)
-- Create donation system database
CREATE DATABASE IF NOT EXISTS donation_db;
USE donation_db;
-- Users table (donors and organization staff)
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
user_type ENUM('donor', 'organization', 'admin') DEFAULT 'donor',
full_name VARCHAR(255) NOT NULL,
phone VARCHAR(50),
profile_image VARCHAR(500),
address TEXT,
city VARCHAR(100),
state VARCHAR(100),
country VARCHAR(100),
postal_code VARCHAR(20),
date_of_birth DATE,
is_verified BOOLEAN DEFAULT FALSE,
email_verified BOOLEAN DEFAULT FALSE,
email_verified_at TIMESTAMP NULL,
last_login TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_user_type (user_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Organizations (non-profits, charities, etc.)
CREATE TABLE IF NOT EXISTS organizations (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL UNIQUE, -- Admin user for the organization
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
mission_statement TEXT,
logo VARCHAR(500),
cover_image VARCHAR(500),
website VARCHAR(500),
facebook VARCHAR(500),
twitter VARCHAR(500),
instagram VARCHAR(500),
phone VARCHAR(50),
email VARCHAR(255),
address TEXT,
city VARCHAR(100),
state VARCHAR(100),
country VARCHAR(100),
postal_code VARCHAR(20),
tax_id VARCHAR(100), -- EIN for US organizations
stripe_account_id VARCHAR(255),
paypal_email VARCHAR(255),
bank_account JSON, -- Encrypted bank details
is_verified BOOLEAN DEFAULT FALSE,
verification_documents JSON,
platform_fee_percentage DECIMAL(5,2) DEFAULT 5.00, -- Custom fee for this org
status ENUM('pending', 'active', 'suspended', 'rejected') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_slug (slug),
INDEX idx_status (status),
FULLTEXT INDEX idx_search (name, description),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Organization staff members
CREATE TABLE IF NOT EXISTS organization_staff (
id INT AUTO_INCREMENT PRIMARY KEY,
organization_id INT NOT NULL,
user_id INT NOT NULL,
role ENUM('admin', 'manager', 'viewer') DEFAULT 'viewer',
permissions JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_staff (organization_id, user_id),
INDEX idx_organization (organization_id),
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Campaign categories
CREATE TABLE IF NOT EXISTS campaign_categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
icon VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Campaigns
CREATE TABLE IF NOT EXISTS campaigns (
id INT AUTO_INCREMENT PRIMARY KEY,
organization_id INT NOT NULL,
category_id INT,
title VARCHAR(500) NOT NULL,
slug VARCHAR(500) UNIQUE NOT NULL,
description TEXT NOT NULL,
short_description VARCHAR(500),
goal_amount DECIMAL(10,2) NOT NULL,
raised_amount DECIMAL(10,2) DEFAULT 0.00,
donor_count INT DEFAULT 0,
featured_image VARCHAR(500),
gallery_images JSON,
video_url VARCHAR(500),
start_date DATETIME NOT NULL,
end_date DATETIME NULL,
is_evergreen BOOLEAN DEFAULT FALSE, -- No end date
is_featured BOOLEAN DEFAULT FALSE,
is_urgent BOOLEAN DEFAULT FALSE,
urgent_reason TEXT,
matching_gift_enabled BOOLEAN DEFAULT FALSE,
matching_gift_amount DECIMAL(10,2) NULL,
matching_gift_donor VARCHAR(255),
matching_gift_expires DATETIME NULL,
allow_tribute BOOLEAN DEFAULT TRUE,
allow_anonymous BOOLEAN DEFAULT TRUE,
suggested_amounts JSON, -- e.g., [25, 50, 100, 250]
minimum_amount DECIMAL(10,2) DEFAULT 1.00,
status ENUM('draft', 'pending', 'active', 'paused', 'completed', 'cancelled') DEFAULT 'draft',
views INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_organization (organization_id),
INDEX idx_category (category_id),
INDEX idx_status (status),
INDEX idx_featured (is_featured),
INDEX idx_urgent (is_urgent),
INDEX idx_end_date (end_date),
FULLTEXT INDEX idx_search (title, description),
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES campaign_categories(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Campaign updates
CREATE TABLE IF NOT EXISTS campaign_updates (
id INT AUTO_INCREMENT PRIMARY KEY,
campaign_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
images JSON,
is_pinned BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_campaign (campaign_id),
FOREIGN KEY (campaign_id) REFERENCES campaigns(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Donations
CREATE TABLE IF NOT EXISTS donations (
id INT AUTO_INCREMENT PRIMARY KEY,
donation_number VARCHAR(50) UNIQUE NOT NULL,
donor_id INT NULL, -- NULL for anonymous
campaign_id INT NOT NULL,
organization_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
platform_fee DECIMAL(10,2) NOT NULL,
net_amount DECIMAL(10,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
payment_method VARCHAR(50) NOT NULL,
payment_intent_id VARCHAR(255),
transaction_id VARCHAR(255),
donation_type ENUM('one_time', 'monthly', 'annual') DEFAULT 'one_time',
recurring_id VARCHAR(255) NULL, -- For recurring donations
is_anonymous BOOLEAN DEFAULT FALSE,
is_tribute BOOLEAN DEFAULT FALSE,
tribute_type ENUM('in_honor', 'in_memory') NULL,
tribute_name VARCHAR(255) NULL,
tribute_notify_name VARCHAR(255) NULL,
tribute_notify_email VARCHAR(255) NULL,
tribute_notify_address TEXT NULL,
dedication_message TEXT,
cover_fees BOOLEAN DEFAULT FALSE, -- Donor covers platform fees
status ENUM('pending', 'completed', 'failed', 'refunded') DEFAULT 'pending',
receipt_sent BOOLEAN DEFAULT FALSE,
receipt_sent_at TIMESTAMP NULL,
receipt_path VARCHAR(500),
donor_email VARCHAR(255), -- For anonymous donations
donor_name VARCHAR(255), -- For anonymous donations
donor_phone VARCHAR(50),
donor_address TEXT,
notes TEXT,
metadata JSON,
completed_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_donor (donor_id),
INDEX idx_campaign (campaign_id),
INDEX idx_organization (organization_id),
INDEX_idx_status (status),
INDEX_idx_created (created_at),
FOREIGN KEY (donor_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (campaign_id) REFERENCES campaigns(id),
FOREIGN KEY (organization_id) REFERENCES organizations(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Recurring donation profiles
CREATE TABLE IF NOT EXISTS recurring_donations (
id INT AUTO_INCREMENT PRIMARY KEY,
recurring_id VARCHAR(255) UNIQUE NOT NULL,
donor_id INT NOT NULL,
campaign_id INT NOT NULL,
organization_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
frequency ENUM('weekly', 'monthly', 'quarterly', 'annual') DEFAULT 'monthly',
day_of_month INT, -- For monthly: 1-31
day_of_week INT, -- For weekly: 0-6 (Sunday-Saturday)
payment_method VARCHAR(50) NOT NULL,
payment_intent_id VARCHAR(255),
payment_details JSON,
is_anonymous BOOLEAN DEFAULT FALSE,
status ENUM('active', 'paused', 'cancelled', 'failed') DEFAULT 'active',
current_period_start TIMESTAMP,
current_period_end TIMESTAMP,
cancelled_at TIMESTAMP NULL,
failed_attempts INT DEFAULT 0,
last_charge_at TIMESTAMP NULL,
next_charge_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_donor (donor_id),
INDEX idx_campaign (campaign_id),
INDEX_idx_status (status),
FOREIGN KEY (donor_id) REFERENCES users(id),
FOREIGN KEY (campaign_id) REFERENCES campaigns(id),
FOREIGN KEY (organization_id) REFERENCES organizations(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tribute notifications
CREATE TABLE IF NOT EXISTS tribute_notifications (
id INT AUTO_INCREMENT PRIMARY KEY,
donation_id INT NOT NULL,
tribute_name VARCHAR(255) NOT NULL,
notify_name VARCHAR(255) NOT NULL,
notify_email VARCHAR(255),
notify_address TEXT,
message TEXT,
notification_sent BOOLEAN DEFAULT FALSE,
sent_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_donation (donation_id),
FOREIGN KEY (donation_id) REFERENCES donations(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Payouts to organizations
CREATE TABLE IF NOT EXISTS payouts (
id INT AUTO_INCREMENT PRIMARY KEY,
payout_number VARCHAR(50) UNIQUE NOT NULL,
organization_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
platform_fee_deducted DECIMAL(10,2) NOT NULL,
net_amount DECIMAL(10,2) NOT NULL,
donation_ids JSON, -- Which donations are included
payment_method ENUM('stripe', 'paypal', 'bank', 'check') NOT NULL,
payment_details JSON,
status ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending',
transaction_id VARCHAR(255),
processed_at TIMESTAMP NULL,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_organization (organization_id),
INDEX idx_status (status),
FOREIGN KEY (organization_id) REFERENCES organizations(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tax receipts
CREATE TABLE IF NOT EXISTS tax_receipts (
id INT AUTO_INCREMENT PRIMARY KEY,
receipt_number VARCHAR(50) UNIQUE NOT NULL,
donation_id INT NOT NULL UNIQUE,
file_path VARCHAR(500) NOT NULL,
generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
emailed_to_donor BOOLEAN DEFAULT FALSE,
emailed_at TIMESTAMP NULL,
INDEX idx_donation (donation_id),
FOREIGN KEY (donation_id) REFERENCES donations(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Donor communications
CREATE TABLE IF NOT EXISTS donor_communications (
id INT AUTO_INCREMENT PRIMARY KEY,
donor_id INT NOT NULL,
type ENUM('thank_you', 'receipt', 'newsletter', 'update', 'appeal') NOT NULL,
subject VARCHAR(255) NOT NULL,
content TEXT,
sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
opened_at TIMESTAMP NULL,
clicked_at TIMESTAMP NULL,
INDEX idx_donor (donor_id),
FOREIGN KEY (donor_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Matching gifts
CREATE TABLE IF NOT EXISTS matching_gifts (
id INT AUTO_INCREMENT PRIMARY KEY,
campaign_id INT NOT NULL,
donor_id INT NOT NULL, -- The donor offering the match
match_amount DECIMAL(10,2) NOT NULL,
remaining_amount DECIMAL(10,2) NOT NULL,
match_ratio DECIMAL(3,2) DEFAULT 1.00, -- e.g., 1:1, 2:1
minimum_donation DECIMAL(10,2) DEFAULT 1.00,
maximum_donation DECIMAL(10,2) NULL,
start_date DATETIME NOT NULL,
end_date DATETIME NULL,
status ENUM('active', 'exhausted', 'expired', 'cancelled') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_campaign (campaign_id),
FOREIGN KEY (campaign_id) REFERENCES campaigns(id),
FOREIGN KEY (donor_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Donation comments
CREATE TABLE IF NOT EXISTS donation_comments (
id INT AUTO_INCREMENT PRIMARY KEY,
donation_id INT NOT NULL,
donor_id INT NULL,
comment TEXT NOT NULL,
is_public BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_donation (donation_id),
FOREIGN KEY (donation_id) REFERENCES donations(id) ON DELETE CASCADE,
FOREIGN KEY (donor_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Platform settings
CREATE TABLE IF NOT EXISTS platform_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_key VARCHAR(255) UNIQUE NOT NULL,
setting_value TEXT,
setting_type ENUM('text', 'number', 'boolean', 'json') DEFAULT 'text',
description TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert default categories
INSERT INTO campaign_categories (name, slug, icon, description) VALUES
('Disaster Relief', 'disaster-relief', '🌊', 'Help communities affected by natural disasters'),
('Medical', 'medical', '🏥', 'Support medical treatments and healthcare'),
('Education', 'education', '📚', 'Fund educational programs and scholarships'),
('Animals', 'animals', '🐾', 'Protect and care for animals'),
('Environment', 'environment', '🌱', 'Support environmental causes'),
('Arts & Culture', 'arts-culture', '🎨', 'Promote arts and cultural preservation'),
('Community Development', 'community', '🏘️', 'Strengthen local communities'),
('Children & Youth', 'children', '👧', 'Support programs for children and youth'),
('Senior Citizens', 'seniors', '👴', 'Assist elderly populations'),
('Veterans', 'veterans', '🎖️', 'Support military veterans'),
('Religious', 'religious', '⛪', 'Religious and faith-based causes'),
('International Aid', 'international', '🌍', 'Global humanitarian efforts');
-- Insert default platform settings
INSERT INTO platform_settings (setting_key, setting_value, setting_type, description) VALUES
('platform_name', 'GiveHope Donations', 'text', 'Platform name'),
('platform_fee_percentage', '5', 'number', 'Default platform fee percentage'),
('platform_fee_fixed', '0.30', 'number', 'Fixed platform fee per transaction'),
('minimum_donation', '1', 'number', 'Minimum donation amount'),
('maximum_donation', '50000', 'number', 'Maximum donation amount per transaction'),
('organization_verification_required', 'true', 'boolean', 'Require verification for organizations'),
('receipt_prefix', 'RCPT', 'text', 'Prefix for receipt numbers'),
('receipt_footer', 'Thank you for your generosity!', 'text', 'Footer text on receipts'),
('tax_deductible_message', 'Your donation is tax-deductible to the extent allowed by law.', 'text', 'Tax disclaimer'),
('currency', 'USD', 'text', 'Default currency'),
('timezone', 'America/New_York', 'text', 'Default timezone');
🔧 Core Configuration Files
1. includes/donation-config.php
<?php
/**
* Donation System Configuration
*/
require_once __DIR__ . '/../../vendor/autoload.php';
use Dotenv\Dotenv;
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/../../');
$dotenv->load();
// Database configuration
define('DB_HOST', $_ENV['DB_HOST']);
define('DB_USER', $_ENV['DB_USER']);
define('DB_PASS', $_ENV['DB_PASS']);
define('DB_NAME', 'donation_db');
// Payment Gateway Keys
define('STRIPE_KEY', $_ENV['STRIPE_KEY'] ?? '');
define('STRIPE_SECRET', $_ENV['STRIPE_SECRET'] ?? '');
define('PAYPAL_CLIENT_ID', $_ENV['PAYPAL_CLIENT_ID'] ?? '');
define('PAYPAL_SECRET', $_ENV['PAYPAL_SECRET'] ?? '');
// Platform Settings
define('PLATFORM_NAME', 'GiveHope Donations');
define('PLATFORM_FEE_PERCENTAGE', 5.0); // 5%
define('PLATFORM_FEE_FIXED', 0.30); // $0.30
define('CURRENCY', 'USD');
define('CURRENCY_SYMBOL', '$');
define('MIN_DONATION', 1.00);
define('MAX_DONATION', 50000.00);
// Receipt Settings
define('RECEIPT_PREFIX', 'RCPT');
define('RECEIPT_FOOTER', 'Thank you for your generosity!');
define('TAX_DEDUCTIBLE_MESSAGE', 'Your donation is tax-deductible to the extent allowed by law.');
// Email Settings
define('EMAIL_FROM', '[email protected]');
define('EMAIL_FROM_NAME', PLATFORM_NAME);
define('THANK_YOU_EMAIL', true);
define('RECEIPT_EMAIL', true);
// Upload Settings
define('MAX_ORGANIZATION_IMAGES', 10);
define('MAX_CAMPAIGN_IMAGES', 20);
define('MAX_FILE_SIZE', 10 * 1024 * 1024); // 10MB
define('ALLOWED_IMAGE_TYPES', ['jpg', 'jpeg', 'png', 'gif', 'webp']);
// Start session
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Database connection (PDO)
function getDB() {
static $pdo = null;
if ($pdo === null) {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]);
} catch (PDOException $e) {
error_log("Database connection failed: " . $e->getMessage());
die("Database connection failed. Please try again later.");
}
}
return $pdo;
}
// Get current user
function getCurrentUser() {
if (isset($_SESSION['user_id'])) {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch();
}
return null;
}
// Check if user is logged in
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
// Require login
function requireLogin() {
if (!isLoggedIn()) {
header('Location: /blog-website/login.php?redirect=' . urlencode($_SERVER['REQUEST_URI']));
exit;
}
}
// Check if user is organization admin
function isOrganizationAdmin($orgId = null) {
$user = getCurrentUser();
if (!$user) return false;
if ($user['user_type'] === 'admin') return true;
if ($user['user_type'] === 'organization') {
if ($orgId) {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT id FROM organizations WHERE id = ? AND user_id = ?");
$stmt->execute([$orgId, $user['id']]);
return $stmt->fetch() ? true : false;
}
return true;
}
return false;
}
// Check if user is admin
function isAdmin() {
$user = getCurrentUser();
return $user && $user['user_type'] === 'admin';
}
// Generate unique donation number
function generateDonationNumber() {
return 'DON' . date('Ymd') . strtoupper(uniqid()) . rand(100, 999);
}
// Generate unique receipt number
function generateReceiptNumber() {
return RECEIPT_PREFIX . date('Y') . str_pad(rand(1, 99999), 5, '0', STR_PAD_LEFT);
}
// Format currency
function formatCurrency($amount) {
return CURRENCY_SYMBOL . number_format($amount, 2);
}
// Calculate platform fee
function calculatePlatformFee($amount) {
return ($amount * PLATFORM_FEE_PERCENTAGE / 100) + PLATFORM_FEE_FIXED;
}
// Calculate net amount after fees
function calculateNetAmount($amount) {
return $amount - calculatePlatformFee($amount);
}
// Sanitize input
function sanitize($input) {
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
// Validate email
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
// Send email
function sendEmail($to, $subject, $body, $from = null) {
$from = $from ?? EMAIL_FROM;
$fromName = EMAIL_FROM_NAME;
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
$headers .= "From: {$fromName} <{$from}>" . "\r\n";
return mail($to, $subject, $body, $headers);
}
2. donate/index.php (Donation Landing Page)
<?php
require_once '../includes/config.php';
require_once '../includes/donation-config.php';
$page_title = 'Make a Difference - ' . PLATFORM_NAME;
$pdo = getDB();
// Get featured campaigns
$featured = $pdo->query("
SELECT c.*,
o.name as organization_name,
o.logo as organization_logo,
(c.raised_amount / c.goal_amount * 100) as percentage
FROM campaigns c
JOIN organizations o ON c.organization_id = o.id
WHERE c.status = 'active' AND c.is_featured = 1
ORDER BY c.created_at DESC
LIMIT 6
")->fetchAll();
// Get urgent campaigns
$urgent = $pdo->query("
SELECT c.*,
o.name as organization_name,
(c.raised_amount / c.goal_amount * 100) as percentage
FROM campaigns c
JOIN organizations o ON c.organization_id = o.id
WHERE c.status = 'active' AND c.is_urgent = 1
ORDER BY c.created_at DESC
LIMIT 3
")->fetchAll();
// Get total stats
$stats = [
'raised' => $pdo->query("SELECT SUM(amount) FROM donations WHERE status = 'completed'")->fetchColumn(),
'donors' => $pdo->query("SELECT COUNT(DISTINCT donor_id) FROM donations WHERE status = 'completed' AND donor_id IS NOT NULL")->fetchColumn(),
'campaigns' => $pdo->query("SELECT COUNT(*) FROM campaigns WHERE status = 'active'")->fetchColumn(),
'organizations' => $pdo->query("SELECT COUNT(*) FROM organizations WHERE status = 'active'")->fetchColumn()
];
include '../includes/header.php';
?>
<link rel="stylesheet" href="css/donate.css">
<div class="donate-container">
<!-- Hero Section -->
<section class="hero-section" style="background: linear-gradient(135deg, #ff6b6b 0%, #c92a6b 100%);">
<div class="hero-content">
<h1>Make a Difference Today</h1>
<p class="hero-subtitle">Your generosity can change lives. Support causes that matter to you.</p>
<div class="stats-banner">
<div class="stat-item">
<span class="stat-number"><?php echo formatCurrency($stats['raised']); ?></span>
<span class="stat-label">Raised</span>
</div>
<div class="stat-item">
<span class="stat-number"><?php echo number_format($stats['donors']); ?></span>
<span class="stat-label">Donors</span>
</div>
<div class="stat-item">
<span class="stat-number"><?php echo number_format($stats['campaigns']); ?></span>
<span class="stat-label">Active Campaigns</span>
</div>
<div class="stat-item">
<span class="stat-number"><?php echo number_format($stats['organizations']); ?></span>
<span class="stat-label">Organizations</span>
</div>
</div>
<div class="hero-actions">
<a href="campaigns.php" class="btn-primary">Browse Campaigns</a>
<a href="#how-it-works" class="btn-secondary">Learn More</a>
</div>
</div>
</section>
<!-- Urgent Appeals -->
<?php if (!empty($urgent)): ?>
<section class="urgent-section">
<h2>⚠️ Urgent Appeals</h2>
<p class="section-subtitle">These campaigns need immediate attention</p>
<div class="urgent-grid">
<?php foreach ($urgent as $campaign): ?>
<div class="urgent-card">
<div class="urgent-content">
<h3><?php echo htmlspecialchars($campaign['title']); ?></h3>
<p class="organization">by <?php echo htmlspecialchars($campaign['organization_name']); ?></p>
<p class="urgent-reason"><?php echo htmlspecialchars($campaign['urgent_reason']); ?></p>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill" style="width: <?php echo min(100, $campaign['percentage']); ?>%"></div>
</div>
<div class="progress-stats">
<span class="raised"><?php echo formatCurrency($campaign['raised_amount']); ?> raised</span>
<span class="goal">of <?php echo formatCurrency($campaign['goal_amount']); ?></span>
</div>
</div>
<a href="campaign.php?id=<?php echo $campaign['id']; ?>" class="btn-donate-now">Donate Now →</a>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<!-- Featured Campaigns -->
<section class="featured-section">
<h2>Featured Campaigns</h2>
<p class="section-subtitle">Hand-picked campaigns making a difference</p>
<div class="campaigns-grid">
<?php foreach ($featured as $campaign): ?>
<div class="campaign-card">
<div class="campaign-image">
<img src="<?php echo $campaign['featured_image'] ?? '/assets/images/campaign-placeholder.jpg'; ?>"
alt="<?php echo htmlspecialchars($campaign['title']); ?>">
<?php if ($campaign['is_urgent']): ?>
<span class="urgent-badge">Urgent</span>
<?php endif; ?>
</div>
<div class="campaign-details">
<div class="organization-info">
<img src="<?php echo $campaign['organization_logo'] ?? '/assets/images/org-placeholder.jpg'; ?>"
alt="<?php echo htmlspecialchars($campaign['organization_name']); ?>"
class="org-logo-small">
<span class="org-name"><?php echo htmlspecialchars($campaign['organization_name']); ?></span>
</div>
<h3><a href="campaign.php?id=<?php echo $campaign['id']; ?>">
<?php echo htmlspecialchars($campaign['title']); ?>
</a></h3>
<p class="campaign-description">
<?php echo htmlspecialchars($campaign['short_description'] ?: substr($campaign['description'], 0, 100) . '...'); ?>
</p>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill" style="width: <?php echo min(100, $campaign['percentage']); ?>%"></div>
</div>
<div class="progress-stats">
<span class="raised"><?php echo formatCurrency($campaign['raised_amount']); ?></span>
<span class="goal">of <?php echo formatCurrency($campaign['goal_amount']); ?></span>
</div>
</div>
<div class="campaign-footer">
<span class="donor-count"><?php echo number_format($campaign['donor_count']); ?> donors</span>
<a href="campaign.php?id=<?php echo $campaign['id']; ?>" class="btn-donate">Donate</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="view-more">
<a href="campaigns.php" class="btn-view-all">View All Campaigns →</a>
</div>
</section>
<!-- How It Works -->
<section id="how-it-works" class="how-it-works">
<h2>How It Works</h2>
<div class="steps">
<div class="step">
<div class="step-icon">🔍</div>
<h3>Find a Cause</h3>
<p>Browse campaigns that resonate with you</p>
</div>
<div class="step">
<div class="step-icon">💝</div>
<h3>Choose Amount</h3>
<p>Select one-time or recurring donation</p>
</div>
<div class="step">
<div class="step-icon">💳</div>
<h3>Donate Securely</h3>
<p>Pay with credit card, PayPal, or bank transfer</p>
</div>
<div class="step">
<div class="step-icon">📧</div>
<h3>Get Receipt</h3>
<p>Receive tax-deductible receipt via email</p>
</div>
</div>
</section>
<!-- Impact Stories -->
<section class="impact-section">
<h2>Your Impact</h2>
<div class="impact-grid">
<div class="impact-card">
<div class="impact-number">500+</div>
<p>Families Supported</p>
</div>
<div class="impact-card">
<div class="impact-number">50+</div>
<p>Communities Reached</p>
</div>
<div class="impact-card">
<div class="impact-number">1000+</div>
<p>Lives Changed</p>
</div>
<div class="impact-card">
<div class="impact-number">24</div>
<p>Countries Impacted</p>
</div>
</div>
</section>
<!-- Organization CTA -->
<section class="organization-cta">
<div class="cta-content">
<h2>Are you a non-profit organization?</h2>
<p>Join our platform to start raising funds for your cause</p>
<a href="organization/register.php" class="btn-organizer">Register Your Organization</a>
</div>
</section>
</div>
<style>
.donate-container {
max-width: 1200px;
margin: 0 auto;
}
/* Hero Section */
.hero-section {
color: white;
padding: 4rem 2rem;
border-radius: 0 0 2rem 2rem;
margin-bottom: 3rem;
text-align: center;
}
.hero-content h1 {
font-size: 3.5rem;
margin-bottom: 1rem;
font-weight: 800;
}
.hero-subtitle {
font-size: 1.3rem;
margin-bottom: 2rem;
opacity: 0.95;
}
.stats-banner {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2rem;
max-width: 800px;
margin: 3rem auto;
background: rgba(255,255,255,0.15);
padding: 2rem;
border-radius: 1rem;
backdrop-filter: blur(10px);
}
.stat-item {
text-align: center;
}
.stat-number {
display: block;
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.3rem;
}
.stat-label {
font-size: 0.9rem;
opacity: 0.9;
}
.hero-actions {
display: flex;
gap: 1rem;
justify-content: center;
}
.btn-primary {
padding: 1rem 2rem;
background: white;
color: #c92a6b;
text-decoration: none;
border-radius: 50px;
font-weight: bold;
transition: transform 0.3s;
}
.btn-secondary {
padding: 1rem 2rem;
background: transparent;
color: white;
text-decoration: none;
border-radius: 50px;
font-weight: bold;
border: 2px solid white;
transition: all 0.3s;
}
.btn-primary:hover,
.btn-secondary:hover {
transform: translateY(-3px);
}
.btn-secondary:hover {
background: white;
color: #c92a6b;
}
/* Section Headers */
h2 {
font-size: 2.5rem;
color: #333;
margin-bottom: 0.5rem;
text-align: center;
}
.section-subtitle {
text-align: center;
color: #888;
margin-bottom: 3rem;
font-size: 1.1rem;
}
/* Urgent Appeals */
.urgent-section {
padding: 3rem 2rem;
background: #fff5f5;
border-radius: 1rem;
margin-bottom: 3rem;
}
.urgent-section h2 {
color: #c92a6b;
}
.urgent-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.urgent-card {
background: white;
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 4px 15px rgba(201,42,107,0.1);
border-left: 4px solid #c92a6b;
}
.urgent-content {
padding: 1.5rem;
}
.urgent-content h3 {
font-size: 1.3rem;
margin-bottom: 0.3rem;
color: #333;
}
.organization {
color: #c92a6b;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.urgent-reason {
background: #fff9f9;
padding: 1rem;
border-radius: 0.5rem;
color: #666;
font-size: 0.95rem;
margin-bottom: 1.5rem;
border-left: 3px solid #ff6b6b;
}
.btn-donate-now {
display: inline-block;
margin-top: 1rem;
color: #c92a6b;
text-decoration: none;
font-weight: 500;
}
.btn-donate-now:hover {
text-decoration: underline;
}
/* Campaigns Grid */
.featured-section {
padding: 3rem 2rem;
}
.campaigns-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 2rem;
}
.campaign-card {
background: white;
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: transform 0.3s;
}
.campaign-card:hover {
transform: translateY(-5px);
}
.campaign-image {
position: relative;
height: 200px;
overflow: hidden;
}
.campaign-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.urgent-badge {
position: absolute;
top: 1rem;
right: 1rem;
background: #c92a6b;
color: white;
padding: 0.3rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 500;
}
.campaign-details {
padding: 1.5rem;
}
.organization-info {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.org-logo-small {
width: 30px;
height: 30px;
border-radius: 50%;
object-fit: cover;
}
.org-name {
color: #666;
font-size: 0.9rem;
}
.campaign-details h3 {
margin-bottom: 0.5rem;
font-size: 1.3rem;
}
.campaign-details h3 a {
color: #333;
text-decoration: none;
}
.campaign-details h3 a:hover {
color: #c92a6b;
}
.campaign-description {
color: #666;
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 1rem;
}
/* Progress Bar */
.progress-container {
margin: 1rem 0;
}
.progress-bar {
height: 8px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #c92a6b, #ff6b6b);
transition: width 0.3s;
}
.progress-stats {
display: flex;
justify-content: space-between;
font-size: 0.9rem;
}
.raised {
font-weight: bold;
color: #333;
}
.goal {
color: #888;
}
.campaign-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.donor-count {
color: #888;
font-size: 0.9rem;
}
.btn-donate {
padding: 0.5rem 1.5rem;
background: #c92a6b;
color: white;
text-decoration: none;
border-radius: 5px;
transition: opacity 0.3s;
}
.btn-donate:hover {
opacity: 0.9;
}
.view-more {
text-align: center;
margin-top: 3rem;
}
.btn-view-all {
display: inline-block;
padding: 1rem 2rem;
background: transparent;
color: #c92a6b;
text-decoration: none;
border: 2px solid #c92a6b;
border-radius: 5px;
font-size: 1.1rem;
transition: all 0.3s;
}
.btn-view-all:hover {
background: #c92a6b;
color: white;
}
/* How It Works */
.how-it-works {
padding: 4rem 2rem;
background: #f9f9f9;
border-radius: 2rem;
margin: 3rem 0;
}
.steps {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2rem;
text-align: center;
}
.step-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.step h3 {
color: #333;
margin-bottom: 0.5rem;
}
.step p {
color: #666;
font-size: 0.95rem;
}
/* Impact Section */
.impact-section {
padding: 4rem 2rem;
text-align: center;
}
.impact-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2rem;
max-width: 800px;
margin: 3rem auto;
}
.impact-card {
padding: 2rem;
background: linear-gradient(135deg, #c92a6b, #ff6b6b);
color: white;
border-radius: 1rem;
box-shadow: 0 4px 15px rgba(201,42,107,0.3);
}
.impact-number {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
/* Organization CTA */
.organization-cta {
background: linear-gradient(135deg, #c92a6b 0%, #ff6b6b 100%);
color: white;
padding: 4rem 2rem;
border-radius: 2rem;
margin: 3rem 0;
text-align: center;
}
.cta-content h2 {
color: white;
font-size: 2.5rem;
margin-bottom: 1rem;
}
.cta-content p {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.95;
}
.btn-organizer {
display: inline-block;
padding: 1rem 3rem;
background: white;
color: #c92a6b;
text-decoration: none;
border-radius: 50px;
font-size: 1.1rem;
font-weight: bold;
transition: transform 0.3s;
}
.btn-organizer:hover {
transform: scale(1.05);
}
/* Responsive */
@media (max-width: 1024px) {
.urgent-grid {
grid-template-columns: repeat(2, 1fr);
}
.steps {
grid-template-columns: repeat(2, 1fr);
}
.impact-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.hero-content h1 {
font-size: 2.5rem;
}
.stats-banner {
grid-template-columns: repeat(2, 1fr);
}
.urgent-grid {
grid-template-columns: 1fr;
}
.campaigns-grid {
grid-template-columns: 1fr;
}
.steps {
grid-template-columns: 1fr;
}
.impact-grid {
grid-template-columns: 1fr;
}
.hero-actions {
flex-direction: column;
}
}
@media (max-width: 480px) {
.stats-banner {
grid-template-columns: 1fr;
}
}
</style>
<?php include '../includes/footer.php'; ?>
3. donate/donate.php (Donation Form)
<?php
require_once '../includes/config.php';
require_once '../includes/donation-config.php';
$campaignId = $_GET['campaign'] ?? 0;
$amount = $_GET['amount'] ?? null;
$pdo = getDB();
// Get campaign details
$stmt = $pdo->prepare("
SELECT c.*, o.name as organization_name, o.id as organization_id
FROM campaigns c
JOIN organizations o ON c.organization_id = o.id
WHERE c.id = ? AND c.status = 'active'
");
$stmt->execute([$campaignId]);
$campaign = $stmt->fetch();
if (!$campaign) {
header('Location: campaigns.php');
exit;
}
// Parse suggested amounts
$suggestedAmounts = json_decode($campaign['suggested_amounts'], true) ?: [25, 50, 100, 250];
$page_title = 'Donate to ' . $campaign['title'];
include '../includes/header.php';
?>
<link rel="stylesheet" href="css/donate.css">
<div class="donate-form-container">
<div class="donate-header">
<h1>Make a Donation</h1>
<p>Your generosity helps <?php echo htmlspecialchars($campaign['organization_name']); ?> continue their important work.</p>
</div>
<div class="donate-content">
<!-- Campaign Summary -->
<div class="campaign-summary">
<div class="summary-header">
<h2><?php echo htmlspecialchars($campaign['title']); ?></h2>
<p class="organization">by <?php echo htmlspecialchars($campaign['organization_name']); ?></p>
</div>
<div class="progress-container">
<div class="progress-bar">
<?php $percentage = ($campaign['raised_amount'] / $campaign['goal_amount']) * 100; ?>
<div class="progress-fill" style="width: <?php echo min(100, $percentage); ?>%"></div>
</div>
<div class="progress-stats">
<span class="raised"><?php echo formatCurrency($campaign['raised_amount']); ?> raised</span>
<span class="goal">of <?php echo formatCurrency($campaign['goal_amount']); ?> goal</span>
</div>
</div>
<div class="donor-count">
<span class="count"><?php echo number_format($campaign['donor_count']); ?> donors</span>
</div>
<?php if ($campaign['matching_gift_enabled'] && $campaign['matching_gift_amount'] > 0): ?>
<div class="matching-gift-banner">
<span class="matching-icon">🎁</span>
<div class="matching-text">
<strong>Matching Gift Available!</strong>
<p>Your donation will be matched up to <?php echo formatCurrency($campaign['matching_gift_amount']); ?></p>
</div>
</div>
<?php endif; ?>
</div>
<!-- Donation Form -->
<form id="donationForm" class="donation-form" method="POST" action="process-donation.php">
<input type="hidden" name="campaign_id" value="<?php echo $campaignId; ?>">
<input type="hidden" name="organization_id" value="<?php echo $campaign['organization_id']; ?>">
<!-- Amount Selection -->
<div class="form-section">
<h3>1. Choose Amount</h3>
<div class="amount-presets">
<?php foreach ($suggestedAmounts as $preset): ?>
<button type="button" class="amount-preset <?php echo $amount == $preset ? 'selected' : ''; ?>"
data-amount="<?php echo $preset; ?>">
<?php echo formatCurrency($preset); ?>
</button>
<?php endforeach; ?>
<div class="custom-amount">
<input type="number"
id="customAmount"
name="amount"
class="form-control"
placeholder="Other amount"
min="<?php echo MIN_DONATION; ?>"
max="<?php echo MAX_DONATION; ?>"
step="0.01"
value="<?php echo $amount ?: ''; ?>">
</div>
</div>
<div class="amount-error" id="amountError"></div>
<div class="cover-fees">
<label class="checkbox-label">
<input type="checkbox" name="cover_fees" id="coverFees">
<span>Cover the platform fees (so 100% of my donation goes to the cause)</span>
</label>
<div class="fee-explanation" id="feeExplanation" style="display: none;">
<p>Adding <?php echo PLATFORM_FEE_PERCENTAGE; ?>% + <?php echo formatCurrency(PLATFORM_FEE_FIXED); ?> helps us cover processing costs.</p>
</div>
</div>
</div>
<!-- Donation Type -->
<div class="form-section">
<h3>2. Donation Type</h3>
<div class="donation-type-options">
<label class="radio-card">
<input type="radio" name="donation_type" value="one_time" checked>
<div class="radio-content">
<span class="radio-title">One-Time Donation</span>
<span class="radio-description">Make a single donation today</span>
</div>
</label>
<label class="radio-card">
<input type="radio" name="donation_type" value="monthly">
<div class="radio-content">
<span class="radio-title">Monthly Donation</span>
<span class="radio-description">Provide ongoing support</span>
</div>
</label>
</div>
<div id="recurringOptions" style="display: none;">
<div class="recurring-frequency">
<label class="radio-label">
<input type="radio" name="frequency" value="monthly" checked> Monthly
</label>
<label class="radio-label">
<input type="radio" name="frequency" value="quarterly"> Quarterly
</label>
<label class="radio-label">
<input type="radio" name="frequency" value="annual"> Annual
</label>
</div>
</div>
</div>
<!-- Donor Information -->
<div class="form-section">
<h3>3. Your Information</h3>
<div class="form-row">
<div class="form-group">
<label for="first_name">First Name *</label>
<input type="text" id="first_name" name="first_name" class="form-control" required>
</div>
<div class="form-group">
<label for="last_name">Last Name *</label>
<input type="text" id="last_name" name="last_name" class="form-control" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="email">Email Address *</label>
<input type="email" id="email" name="email" class="form-control" required>
</div>
<div class="form-group">
<label for="phone">Phone Number</label>
<input type="tel" id="phone" name="phone" class="form-control">
</div>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="anonymous" id="anonymous">
<span>Make this donation anonymous</span>
</label>
</div>
</div>
<!-- Tribute Information -->
<div class="form-section">
<h3>4. Tribute Information (Optional)</h3>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="is_tribute" id="isTribute">
<span>This donation is in honor or memory of someone</span>
</label>
</div>
<div id="tributeOptions" style="display: none;">
<div class="form-row">
<div class="form-group">
<label for="tribute_type">Tribute Type</label>
<select id="tribute_type" name="tribute_type" class="form-control">
<option value="in_honor">In Honor Of</option>
<option value="in_memory">In Memory Of</option>
</select>
</div>
<div class="form-group">
<label for="tribute_name">Name *</label>
<input type="text" id="tribute_name" name="tribute_name" class="form-control">
</div>
</div>
<div class="form-group">
<label for="dedication_message">Dedication Message</label>
<textarea id="dedication_message" name="dedication_message" class="form-control" rows="3"></textarea>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="notify_tribute" id="notifyTribute">
<span>Notify someone about this tribute</span>
</label>
</div>
<div id="tributeNotification" style="display: none;">
<div class="form-row">
<div class="form-group">
<label for="tribute_notify_name">Recipient Name *</label>
<input type="text" id="tribute_notify_name" name="tribute_notify_name" class="form-control">
</div>
<div class="form-group">
<label for="tribute_notify_email">Recipient Email</label>
<input type="email" id="tribute_notify_email" name="tribute_notify_email" class="form-control">
</div>
</div>
<div class="form-group">
<label for="tribute_notify_address">Recipient Address</label>
<textarea id="tribute_notify_address" name="tribute_notify_address" class="form-control" rows="2"></textarea>
</div>
</div>
</div>
</div>
<!-- Payment Information -->
<div class="form-section">
<h3>5. Payment Information</h3>
<div class="payment-methods">
<label class="payment-method">
<input type="radio" name="payment_method" value="stripe" checked>
<span class="method-icon">💳</span>
<span class="method-name">Credit Card</span>
</label>
<label class="payment-method">
<input type="radio" name="payment_method" value="paypal">
<span class="method-icon">🅿️</span>
<span class="method-name">PayPal</span>
</label>
<label class="payment-method">
<input type="radio" name="payment_method" value="bank">
<span class="method-icon">🏦</span>
<span class="method-name">Bank Transfer</span>
</label>
</div>
<!-- Stripe Card Element -->
<div id="stripeElement" class="payment-element">
<div id="card-element" class="form-control">
<!-- Stripe will insert card element here -->
</div>
<div id="card-errors" class="error-message"></div>
</div>
<!-- PayPal Button (hidden initially) -->
<div id="paypalButton" class="payment-element" style="display: none;">
<div id="paypal-button-container"></div>
</div>
<!-- Bank Transfer Instructions (hidden initially) -->
<div id="bankInstructions" class="payment-element" style="display: none;">
<div class="bank-info">
<h4>Bank Transfer Information</h4>
<p>Please transfer your donation to:</p>
<ul>
<li><strong>Bank:</strong> Example Bank</li>
<li><strong>Account Name:</strong> GiveHope Foundation</li>
<li><strong>Account Number:</strong> 1234567890</li>
<li><strong>Routing Number:</strong> 021000021</li>
<li><strong>Reference:</strong> DON-<?php echo date('Ymd'); ?>-YOURNAME</li>
</ul>
<p class="note">Your donation will be marked as pending until we receive the transfer.</p>
</div>
</div>
</div>
<!-- Order Summary -->
<div class="order-summary">
<h3>Donation Summary</h3>
<div class="summary-row">
<span>Donation Amount:</span>
<span id="displayAmount">$0.00</span>
</div>
<div class="summary-row" id="platformFeeRow" style="display: none;">
<span>Platform Fee (you cover):</span>
<span id="displayFee">$0.00</span>
</div>
<div class="summary-row total">
<span>Total:</span>
<span id="displayTotal">$0.00</span>
</div>
</div>
<!-- Submit Button -->
<button type="submit" class="btn-donate-submit" id="submitDonation">
Complete Donation
</button>
<div class="secure-note">
<span class="lock-icon">🔒</span>
Secure donation processing powered by Stripe
</div>
</form>
</div>
</div>
<!-- Include Stripe.js -->
<script src="https://js.stripe.com/v3/"></script>
<script>
// Initialize Stripe
const stripe = Stripe('<?php echo STRIPE_KEY; ?>');
const elements = stripe.elements();
const cardElement = elements.create('card');
document.addEventListener('DOMContentLoaded', function() {
// Mount card element
cardElement.mount('#card-element');
// Handle validation errors
cardElement.on('change', function(event) {
const displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Amount preset selection
const presets = document.querySelectorAll('.amount-preset');
const customAmount = document.getElementById('customAmount');
presets.forEach(preset => {
preset.addEventListener('click', function() {
presets.forEach(p => p.classList.remove('selected'));
this.classList.add('selected');
customAmount.value = this.dataset.amount;
updateSummary();
});
});
customAmount.addEventListener('input', function() {
presets.forEach(p => p.classList.remove('selected'));
updateSummary();
});
// Cover fees toggle
const coverFees = document.getElementById('coverFees');
const feeExplanation = document.getElementById('feeExplanation');
coverFees.addEventListener('change', function() {
feeExplanation.style.display = this.checked ? 'block' : 'none';
updateSummary();
});
// Donation type toggle
const donationTypes = document.querySelectorAll('input[name="donation_type"]');
const recurringOptions = document.getElementById('recurringOptions');
donationTypes.forEach(type => {
type.addEventListener('change', function() {
recurringOptions.style.display = this.value !== 'one_time' ? 'block' : 'none';
});
});
// Tribute toggle
const isTribute = document.getElementById('isTribute');
const tributeOptions = document.getElementById('tributeOptions');
isTribute.addEventListener('change', function() {
tributeOptions.style.display = this.checked ? 'block' : 'none';
});
// Notify tribute toggle
const notifyTribute = document.getElementById('notifyTribute');
const tributeNotification = document.getElementById('tributeNotification');
notifyTribute.addEventListener('change', function() {
tributeNotification.style.display = this.checked ? 'block' : 'none';
});
// Payment method toggle
const paymentMethods = document.querySelectorAll('input[name="payment_method"]');
const stripeElement = document.getElementById('stripeElement');
const paypalButton = document.getElementById('paypalButton');
const bankInstructions = document.getElementById('bankInstructions');
paymentMethods.forEach(method => {
method.addEventListener('change', function() {
stripeElement.style.display = 'none';
paypalButton.style.display = 'none';
bankInstructions.style.display = 'none';
if (this.value === 'stripe') {
stripeElement.style.display = 'block';
} else if (this.value === 'paypal') {
paypalButton.style.display = 'block';
loadPayPalButton();
} else if (this.value === 'bank') {
bankInstructions.style.display = 'block';
}
});
});
// Form submission
const form = document.getElementById('donationForm');
form.addEventListener('submit', async function(event) {
event.preventDefault();
// Validate amount
const amount = parseFloat(customAmount.value);
if (!amount || amount < <?php echo MIN_DONATION; ?>) {
showError('amountError', 'Please enter a valid donation amount');
return;
}
if (amount > <?php echo MAX_DONATION; ?>) {
showError('amountError', 'Donation amount exceeds maximum');
return;
}
// Validate required fields
const firstName = document.getElementById('first_name').value;
const lastName = document.getElementById('last_name').value;
const email = document.getElementById('email').value;
if (!firstName || !lastName) {
alert('Please enter your full name');
return;
}
if (!email || !validateEmail(email)) {
alert('Please enter a valid email address');
return;
}
// Disable submit button
const submitBtn = document.getElementById('submitDonation');
submitBtn.disabled = true;
submitBtn.textContent = 'Processing...';
// Get selected payment method
const paymentMethod = document.querySelector('input[name="payment_method"]:checked').value;
if (paymentMethod === 'stripe') {
// Process Stripe payment
const { paymentIntent, error } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: firstName + ' ' + lastName,
email: email
}
}
});
if (error) {
showError('card-errors', error.message);
submitBtn.disabled = false;
submitBtn.textContent = 'Complete Donation';
} else {
// Submit form data
submitFormData();
}
} else {
// Submit form data for other payment methods
submitFormData();
}
});
function showError(elementId, message) {
const element = document.getElementById(elementId);
element.textContent = message;
element.style.display = 'block';
}
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
function updateSummary() {
const amount = parseFloat(document.getElementById('customAmount').value) || 0;
const coverFees = document.getElementById('coverFees').checked;
const platformFee = (amount * <?php echo PLATFORM_FEE_PERCENTAGE / 100; ?>) + <?php echo PLATFORM_FEE_FIXED; ?>;
const total = coverFees ? amount + platformFee : amount;
document.getElementById('displayAmount').textContent = formatCurrency(amount);
document.getElementById('platformFeeRow').style.display = coverFees ? 'flex' : 'none';
if (coverFees) {
document.getElementById('displayFee').textContent = formatCurrency(platformFee);
}
document.getElementById('displayTotal').textContent = formatCurrency(total);
}
function formatCurrency(amount) {
return '<?php echo CURRENCY_SYMBOL; ?>' + amount.toFixed(2);
}
function loadPayPalButton() {
// PayPal button implementation would go here
console.log('Loading PayPal button...');
}
function submitFormData() {
// Create form data and submit via AJAX
const formData = new FormData(document.getElementById('donationForm'));
fetch('process-donation.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = 'confirmation.php?id=' + data.donation_id;
} else {
alert('Error: ' + data.error);
document.getElementById('submitDonation').disabled = false;
document.getElementById('submitDonation').textContent = 'Complete Donation';
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred. Please try again.');
document.getElementById('submitDonation').disabled = false;
document.getElementById('submitDonation').textContent = 'Complete Donation';
});
}
// Initial summary update
updateSummary();
});
</script>
<style>
.donate-form-container {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
.donate-header {
text-align: center;
margin-bottom: 2rem;
}
.donate-header h1 {
font-size: 2.5rem;
color: #333;
margin-bottom: 0.5rem;
}
.donate-header p {
color: #666;
font-size: 1.1rem;
}
.donate-content {
display: grid;
grid-template-columns: 300px 1fr;
gap: 2rem;
}
/* Campaign Summary */
.campaign-summary {
background: linear-gradient(135deg, #c92a6b, #ff6b6b);
color: white;
padding: 1.5rem;
border-radius: 1rem;
height: fit-content;
}
.summary-header h2 {
color: white;
font-size: 1.3rem;
margin-bottom: 0.3rem;
text-align: left;
}
.organization {
color: rgba(255,255,255,0.9);
font-size: 0.9rem;
margin-bottom: 1.5rem;
}
.progress-bar {
background: rgba(255,255,255,0.3);
}
.progress-fill {
background: white;
}
.progress-stats {
color: white;
}
.donor-count {
margin-top: 1rem;
text-align: center;
font-size: 0.9rem;
opacity: 0.9;
}
.matching-gift-banner {
margin-top: 1.5rem;
padding: 1rem;
background: rgba(255,255,255,0.2);
border-radius: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.matching-icon {
font-size: 1.5rem;
}
.matching-text strong {
display: block;
margin-bottom: 0.2rem;
}
.matching-text p {
font-size: 0.85rem;
opacity: 0.9;
}
/* Donation Form */
.donation-form {
background: white;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.form-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #eee;
}
.form-section h3 {
color: #333;
font-size: 1.1rem;
margin-bottom: 1rem;
}
/* Amount Selection */
.amount-presets {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
margin-bottom: 1rem;
}
.amount-preset {
padding: 1rem;
background: #f8f9fa;
border: 2px solid transparent;
border-radius: 5px;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s;
}
.amount-preset:hover {
background: #e9ecef;
}
.amount-preset.selected {
background: #c92a6b;
color: white;
border-color: #a02254;
}
.custom-amount input {
width: 100%;
padding: 1rem;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 1.1rem;
}
.custom-amount input:focus {
border-color: #c92a6b;
outline: none;
}
.amount-error {
color: #dc3545;
font-size: 0.9rem;
margin-top: 0.5rem;
display: none;
}
.cover-fees {
margin-top: 1rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
}
.fee-explanation {
margin-top: 0.5rem;
padding: 0.5rem;
background: #e9ecef;
border-radius: 3px;
font-size: 0.9rem;
color: #666;
}
/* Donation Type */
.donation-type-options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.radio-card {
border: 2px solid #ddd;
border-radius: 5px;
padding: 1rem;
cursor: pointer;
transition: border-color 0.3s;
}
.radio-card:hover {
border-color: #c92a6b;
}
.radio-card input[type="radio"] {
display: none;
}
.radio-card input[type="radio"]:checked + .radio-content .radio-title {
color: #c92a6b;
}
.radio-title {
display: block;
font-weight: 500;
margin-bottom: 0.3rem;
}
.radio-description {
font-size: 0.85rem;
color: #888;
}
.recurring-frequency {
display: flex;
gap: 1rem;
margin-top: 1rem;
}
.radio-label {
display: flex;
align-items: center;
gap: 0.3rem;
cursor: pointer;
}
/* Form Elements */
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.3rem;
color: #333;
font-weight: 500;
font-size: 0.9rem;
}
.form-control {
width: 100%;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1rem;
}
.form-control:focus {
border-color: #c92a6b;
outline: none;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
color: #666;
}
/* Payment Methods */
.payment-methods {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
}
.payment-method {
border: 2px solid #ddd;
border-radius: 5px;
padding: 1rem;
cursor: pointer;
text-align: center;
transition: border-color 0.3s;
}
.payment-method:hover {
border-color: #c92a6b;
}
.payment-method input[type="radio"] {
display: none;
}
.payment-method input[type="radio"]:checked + .method-icon + .method-name {
color: #c92a6b;
}
.method-icon {
display: block;
font-size: 2rem;
margin-bottom: 0.5rem;
}
.method-name {
font-size: 0.9rem;
}
.payment-element {
margin-top: 1.5rem;
}
#card-element {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 5px;
}
.error-message {
color: #dc3545;
font-size: 0.9rem;
margin-top: 0.5rem;
}
.bank-info {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 5px;
}
.bank-info h4 {
color: #333;
margin-bottom: 1rem;
}
.bank-info ul {
list-style: none;
padding: 0;
}
.bank-info li {
margin-bottom: 0.5rem;
color: #666;
}
.bank-info .note {
margin-top: 1rem;
font-style: italic;
color: #888;
}
/* Order Summary */
.order-summary {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 5px;
margin: 1.5rem 0;
}
.order-summary h3 {
color: #333;
margin-bottom: 1rem;
font-size: 1rem;
}
.summary-row {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
color: #666;
}
.summary-row.total {
margin-top: 1rem;
padding-top: 1rem;
border-top: 2px solid #dee2e6;
font-weight: bold;
color: #333;
font-size: 1.2rem;
}
/* Submit Button */
.btn-donate-submit {
width: 100%;
padding: 1.2rem;
background: linear-gradient(135deg, #c92a6b, #ff6b6b);
color: white;
border: none;
border-radius: 5px;
font-size: 1.2rem;
font-weight: 500;
cursor: pointer;
transition: opacity 0.3s;
}
.btn-donate-submit:hover {
opacity: 0.9;
}
.btn-donate-submit:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.secure-note {
text-align: center;
margin-top: 1rem;
color: #888;
font-size: 0.9rem;
}
.lock-icon {
margin-right: 0.3rem;
}
/* Responsive */
@media (max-width: 768px) {
.donate-content {
grid-template-columns: 1fr;
}
.amount-presets {
grid-template-columns: repeat(2, 1fr);
}
.donation-type-options {
grid-template-columns: 1fr;
}
.payment-methods {
grid-template-columns: 1fr;
}
.form-row {
grid-template-columns: 1fr;
}
}
</style>
<?php include '../includes/footer.php'; ?>
🚀 How to Use This Project Step by Step
Step 1: Environment Setup
- Ensure PHP 7.4+ with required extensions
- Install Composer
- Create MySQL database
Step 2: Install Dependencies
composer require vlucas/phpdotenv composer require stripe/stripe-php composer require paypal/rest-api-sdk-php composer require dompdf/dompdf
Step 3: Database Setup
- Import
database/donations.sqlto create tables - Update
.envwith database credentials
Step 4: Configure Payment Gateways
- Set up Stripe account and get API keys
- Set up PayPal business account
- Update
.envwith payment credentials
Step 5: Directory Permissions
chmod -R 777 uploads/organizations/ chmod -R 777 uploads/campaigns/ chmod -R 777 uploads/receipts/
Step 6: Test the System
- Register as an organization
- Create a campaign
- Make a test donation
- Verify receipt generation
🔒 Security Features
Payment Security
- ✅ PCI DSS compliant payment processing
- ✅ SSL/TLS encryption
- ✅ Tokenization of payment data
- ✅ 3D Secure authentication
Donor Protection
- ✅ Anonymous donation option
- ✅ Tax receipt generation
- ✅ Refund processing
- ✅ Fraud detection
Data Protection
- ✅ GDPR compliance tools
- ✅ Data encryption at rest
- ✅ Secure session management
- ✅ Input validation and sanitization
📊 Features Summary
| Feature | Donor | Organization | Admin |
|---|---|---|---|
| Browse Campaigns | ✅ | ✅ | ✅ |
| Make Donation | ✅ | ✅ | ✅ |
| Recurring Donations | ✅ | ✅ | ✅ |
| View History | ✅ | ✅ | ✅ |
| Tax Receipts | ✅ | ✅ | ✅ |
| Create Campaigns | ❌ | ✅ | ✅ |
| Donor Management | ❌ | ✅ | ✅ |
| Fundraising Reports | ❌ | ✅ | ✅ |
| Request Payout | ❌ | ✅ | ✅ |
| Organization Verification | ❌ | ✅ | ✅ |
| Platform Settings | ❌ | ❌ | ✅ |
| Fee Configuration | ❌ | ❌ | ✅ |
| Process Payouts | ❌ | ❌ | ✅ |
| Fraud Monitoring | ❌ | ❌ | ✅ |
🚀 Future Enhancements
- Peer-to-Peer Fundraising: Supporters create personal fundraising pages
- Event Ticketing: Integrate with ticket system for fundraising events
- Auction System: Online charity auctions
- Volunteer Hours: Track and convert volunteer hours to donations
- Corporate Matching: Automated corporate gift matching
- Crowdfunding: Flexible funding goals with all-or-nothing options
- Mobile App: Native iOS/Android apps
- Crypto Donations: Accept Bitcoin and other cryptocurrencies
- Recurring Upgrade: Allow donors to increase recurring amounts
- Impact Reporting: Show donors the impact of their donations
📝 Conclusion
This Online Donation System provides a complete solution for non-profits, charities, and individuals to accept donations online. With features like recurring donations, tribute gifts, tax receipts, and comprehensive reporting, it's perfect for organizations of any size. The system is built with security and compliance in mind, ensuring donor trust and regulatory compliance.