Skip to main content

Overview

Donations are time-bound fundraising campaigns created by pastors for their churches. Each donation campaign has specific goals, bank details, contact information, and tracks individual contributions from members.

Donation Schema

The donation data model captures all aspects of a fundraising campaign:
{
  church: {
    type: ObjectId,
    ref: "Church",
    required: true
  },
  donationName: {
    type: String,
    required: true
  },
  donationStatus: {
    type: String,
    required: true,
    enum: ["on", "off", "completed"]
  },
  startDate: {
    type: Date,
    required: true,
    validate: {
      validator: (value) => value > new Date(),
      message: "Start date must be in the future"
    }
  },
  endDate: {
    type: Date,
    required: true,
    validate: {
      validator: function(value) {
        return value > this.startDate && value > new Date();
      },
      message: "End date must be after start date and in the future"
    }
  },
  donationDescription: {
    type: String,
    required: true
  },
  bankDetails: {
    accountName: { type: String, required: true },
    accountNumber: { type: String, required: true },
    bankName: { type: String, required: true }
  },
  donationSupportContact: {
    phone: { type: String, required: true },
    email: {
      type: String,
      required: true,
      lowercase: true,
      match: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/
    }
  },
  donationMetrics: {
    targetAmount: { type: Number, required: true },
    minAmount: { type: Number, required: true },
    totalGotten: { type: Number, default: 0 }
  },
  donators: [
    {
      user: { type: ObjectId, ref: "User" },
      donated: Number,
      transactionId: String,
      paymentVerified: { type: Boolean, default: false }
    }
  ],
  createdAt: Date,
  updatedAt: Date
}
See the complete schema in donationsmodel.js:3-88

Donation Status

Donation campaigns can be in one of three states:
  • Campaign is currently accepting donations
  • Members can view and contribute to the campaign
  • Within the start and end date window
  • Default status for new campaigns
  • Campaign is temporarily disabled
  • Donations are not being accepted
  • Can be reactivated by pastor
  • Used for temporary holds or maintenance
  • Campaign has reached its goal or end date
  • No longer accepting donations
  • Final state, typically not reversible
  • Historical record maintained
Only pastors can change the donation status. Members can only view campaigns with status “on”.

Campaign Timeline

Start Date

The campaign start date determines when donations can begin:
startDate: {
  type: Date,
  required: true,
  validate: {
    validator: function (value) {
      return value > new Date();
    },
    message: "Start date must be in the future"
  }
}
See validation in donationsmodel.js:23-32
The start date must be in the future when creating a new campaign. This prevents backdating campaigns and ensures proper scheduling.

End Date

The campaign end date has dual validation:
endDate: {
  type: Date,
  required: true,
  validate: {
    validator: function (value) {
      return value > this.startDate && value > new Date();
    },
    message: "End date must be after start date and in the future"
  }
}
See validation in donationsmodel.js:33-42
Validation rules:
  1. Must be after the start date
  2. Must be in the future
Both date validations ensure campaigns are properly scheduled and prevent invalid date ranges.

Bank Details

Each donation campaign includes banking information for receiving funds:
bankDetails: {
  accountName: { type: String, required: true },
  accountNumber: { type: String, required: true },
  bankName: { type: String, required: true }
}
See schema in donationsmodel.js:47-54
Required fields:
  • accountName - Name on the bank account
  • accountNumber - Bank account number for deposits
  • bankName - Name of the banking institution
Store bank account numbers as strings to preserve leading zeros and support international formats.

Support Contact

Donation-specific contact information for donor inquiries:
donationSupportContact: {
  phone: { type: String, required: true },
  email: {
    type: String,
    required: true,
    lowercase: true,
    match: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/
  }
}
See schema in donationsmodel.js:55-66
  • Separate from church contact - Allows campaign-specific support
  • Email validation - Ensures proper email format
  • Automatic lowercase - Normalizes email addresses

Donation Metrics

Metrics track the financial goals and progress of the campaign:
donationMetrics: {
  targetAmount: { type: Number, required: true },
  minAmount: { type: Number, required: true },
  totalGotten: { type: Number, default: 0 }
}
See schema in donationsmodel.js:67-74

Target Amount

The fundraising goal for the campaign:
  • Required field
  • Set by pastor during campaign creation
  • Used to calculate progress percentage
  • Campaign may be marked “completed” when reached

Minimum Amount

The minimum donation accepted per transaction:
  • Required field
  • Enforced during donation processing
  • Prevents micro-transactions
  • Can vary by campaign type

Total Gotten

Running total of all verified donations:
  • Defaults to 0
  • Updated when donations are verified
  • Sum of all donated amounts in the donators array
  • Only counts payments where paymentVerified: true
Calculate campaign progress as: (totalGotten / targetAmount) * 100

Donators Tracking

The system tracks individual contributions in the donators array:
donators: [
  {
    user: { type: ObjectId, ref: "User" },
    donated: Number,
    transactionId: String,
    paymentVerified: { type: Boolean, default: false }
  }
]
See schema in donationsmodel.js:75-85

Donator Record Structure

  • ObjectId reference to User document
  • Optional (allows anonymous donations)
  • Links donation to member profile
  • Used for donation history
  • Numeric value of the donation
  • Currency assumed from church settings
  • Must meet or exceed minAmount
  • Included in totalGotten when verified
  • Unique identifier from payment processor
  • Used for reconciliation
  • Required for payment verification
  • String format for flexibility
  • Boolean flag for payment confirmation
  • Defaults to false
  • Set to true after payment verification
  • Only verified payments count toward totalGotten

Multiple Donations

Users can donate multiple times to the same campaign:
{
  "donators": [
    {
      "user": "507f1f77bcf86cd799439011",
      "donated": 50,
      "transactionId": "txn_abc123",
      "paymentVerified": true
    },
    {
      "user": "507f1f77bcf86cd799439011",
      "donated": 100,
      "transactionId": "txn_def456",
      "paymentVerified": true
    }
  ]
}
Each donation creates a new entry in the donators array, even if from the same user. This maintains a complete transaction history.

Relationships

Donation → Church

  • Many-to-one relationship
  • Required reference
  • Each donation belongs to exactly one church
  • Churches can have multiple donation campaigns

Donation → Users (Donators)

  • Many-to-many relationship through embedded documents
  • Optional references (supports anonymous donations)
  • Tracks individual contributions with metadata

Common Operations

Creating a Donation Campaign

Only users with the pastor role can create donation campaigns.
Required fields:
  • Church ID
  • Campaign name
  • Status (“on”, “off”, or “completed”)
  • Start and end dates (both in the future)
  • Description
  • Bank details (account name, number, bank name)
  • Support contact (phone and email)
  • Metrics (target amount, minimum amount)
Example request:
{
  "church": "507f1f77bcf86cd799439011",
  "donationName": "Building Fund 2026",
  "donationStatus": "on",
  "startDate": "2026-04-01T00:00:00Z",
  "endDate": "2026-12-31T23:59:59Z",
  "donationDescription": "Fundraising for new church building construction",
  "bankDetails": {
    "accountName": "Grace Community Church",
    "accountNumber": "1234567890",
    "bankName": "First National Bank"
  },
  "donationSupportContact": {
    "phone": "555-0123-4567",
    "email": "[email protected]"
  },
  "donationMetrics": {
    "targetAmount": 100000,
    "minAmount": 10,
    "totalGotten": 0
  }
}

Making a Donation

Members can contribute to active campaigns:
  1. Verify campaign status is “on”
  2. Check amount meets minAmount requirement
  3. Process payment through payment gateway
  4. Create donator record with transaction ID
  5. Mark paymentVerified: false initially
  6. Verify payment asynchronously
  7. Update to paymentVerified: true
  8. Increment totalGotten
Example donator record:
{
  "user": "507f1f77bcf86cd799439012",
  "donated": 150,
  "transactionId": "txn_abc123xyz",
  "paymentVerified": true
}

Updating Campaign Status

Pastors can change the campaign status:
  • on → off: Pause campaign temporarily
  • off → on: Resume accepting donations
  • on/off → completed: Mark campaign as finished
Automate status changes based on dates: set to “on” at startDate and “completed” at endDate.

Validation Rules

FieldValidation
churchRequired, must be valid Church ObjectId
donationNameRequired
donationStatusRequired, enum: “on”, “off”, “completed”
startDateRequired, must be in the future
endDateRequired, must be after startDate and in the future
donationDescriptionRequired
bankDetails.*All fields required
donationSupportContact.phoneRequired
donationSupportContact.emailRequired, must match email pattern
donationMetrics.targetAmountRequired, numeric
donationMetrics.minAmountRequired, numeric
donationMetrics.totalGottenDefaults to 0
donators[].userOptional ObjectId
donators[].paymentVerifiedDefaults to false

Best Practices

  1. Verify payments before updating totals - Only count verified donations in totalGotten
  2. Validate date ranges - Ensure start date < end date and both are in the future
  3. Enforce minimum amounts - Check donation amount ≥ minAmount before processing
  4. Store transaction IDs - Always capture payment processor transaction IDs
  5. Separate contact info - Use campaign-specific contacts for better donor support
  6. Handle anonymous donations - Allow user field to be null for privacy
  7. Track verification status - Use paymentVerified flag for reconciliation

Build docs developers (and LLMs) love