top of page

We need to add a Payment Plan system to community.givehub.com, starting with Custom Forms.

Goal: allow organizations to create custom forms where the submitter can either:

  1. Enroll in a simple recurring payment plan

  2. Choose between Pay in Full or Installments inside the form checkout

This should work for fees, tuition, camps, retreats, sponsorships, program payments, mission trips, pledges, etc.

 

Phase 1: Database

Create migrations for the following tables.

1. payment_plan_templates

Stores reusable payment plan settings tied to a custom form field/block.

CREATE TABLE payment_plan_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
form_id UUID NULL,
form_field_id UUID NULL,

name TEXT NOT NULL,
description TEXT NULL,

total_amount_cents INTEGER NOT NULL,
currency TEXT NOT NULL DEFAULT 'USD',

allow_pay_in_full BOOLEAN NOT NULL DEFAULT true,
allow_payment_plan BOOLEAN NOT NULL DEFAULT true,

down_payment_required BOOLEAN NOT NULL DEFAULT false,
down_payment_amount_cents INTEGER NULL,

installment_count INTEGER NOT NULL,
frequency TEXT NOT NULL DEFAULT 'monthly',
start_timing TEXT NOT NULL DEFAULT 'immediate',
start_date DATE NULL,

fund_id UUID NULL,
category_id UUID NULL,

allow_card BOOLEAN NOT NULL DEFAULT true,
allow_ach BOOLEAN NOT NULL DEFAULT true,

auto_charge BOOLEAN NOT NULL DEFAULT true,

reminder_days_before INTEGER NOT NULL DEFAULT 3,
retry_failed_payments BOOLEAN NOT NULL DEFAULT true,
max_retry_attempts INTEGER NOT NULL DEFAULT 3,

authorization_text TEXT NULL,

is_active BOOLEAN NOT NULL DEFAULT true,

created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

 

 

2. payment_plans

Represents an individual person’s enrolled plan.

CREATE TABLE payment_plans (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,

template_id UUID NULL REFERENCES payment_plan_templates(id) ON DELETE SET NULL,
form_id UUID NULL,
form_submission_id UUID NULL,

person_id UUID NULL,
donor_name TEXT NULL,
donor_email TEXT NOT NULL,
donor_phone TEXT NULL,

name TEXT NOT NULL,

total_amount_cents INTEGER NOT NULL,
down_payment_amount_cents INTEGER NOT NULL DEFAULT 0,
installment_amount_cents INTEGER NOT NULL,
installment_count INTEGER NOT NULL,
frequency TEXT NOT NULL DEFAULT 'monthly',

currency TEXT NOT NULL DEFAULT 'USD',

fund_id UUID NULL,
category_id UUID NULL,

payment_method_id UUID NULL,
gateway_provider TEXT NULL,

status TEXT NOT NULL DEFAULT 'active',
/*
active
completed
paused
canceled
failed
*/

next_charge_date DATE NULL,
completed_at TIMESTAMPTZ NULL,
canceled_at TIMESTAMPTZ NULL,
cancel_reason TEXT NULL,

authorization_accepted BOOLEAN NOT NULL DEFAULT false,
authorization_text TEXT NULL,
authorization_accepted_at TIMESTAMPTZ NULL,
authorization_ip TEXT NULL,

created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

 

 

3. payment_plan_installments

Each scheduled payment.

CREATE TABLE payment_plan_installments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
payment_plan_id UUID NOT NULL REFERENCES payment_plans(id) ON DELETE CASCADE,

installment_number INTEGER NOT NULL,
amount_cents INTEGER NOT NULL,
due_date DATE NOT NULL,

status TEXT NOT NULL DEFAULT 'scheduled',
/*
scheduled
processing
paid
failed
skipped
canceled
*/

payment_id UUID NULL,
contribution_id UUID NULL,

attempted_at TIMESTAMPTZ NULL,
paid_at TIMESTAMPTZ NULL,
failed_at TIMESTAMPTZ NULL,

retry_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT NULL,

created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),

UNIQUE(payment_plan_id, installment_number)
);

 

 

Phase 2: Custom Form Builder

Add a new field/block type:

payment_plan

In the custom form builder, org admins should be able to add a Payment Plan block.

Admin configuration fields

The Payment Plan block should allow:

  • Plan name

  • Description

  • Total amount

  • Allow Pay in Full: yes/no

  • Allow Payment Plan: yes/no

  • Number of installments

  • Frequency:

    • weekly

    • biweekly

    • monthly

    • quarterly

  • Down payment required: yes/no

  • Down payment amount

  • Start timing:

    • immediately after form submission

    • specific date

    • first day of next month

  • Fund

  • Category

  • Allow card

  • Allow ACH

  • Auto-charge installments

  • Reminder days before charge

  • Failed payment retry attempts

  • Custom authorization text

Default authorization text:

I authorize this organization and GiveHub to charge my selected payment method according to the payment schedule shown above. I understand that I may contact the organization with questions about this payment plan.

Phase 3: Public Form Experience

When a public user fills out a form that contains a Payment Plan block, show a clear checkout section.

If both payment options are enabled

Display:

Choose payment option:
[ ] Pay in full today
[ ] Payment plan

Pay in full

Charge the full amount immediately using existing GiveHub payment flow.

Create a normal contribution/payment record.

Do not create an active payment plan unless needed for reporting. If easier, create a payment plan with status completed, but do not schedule installments.

Payment plan

Show:

  • Total amount

  • Down payment today, if required

  • Number of installments

  • Installment amount

  • Frequency

  • First charge date

  • Final estimated charge date

  • Payment method section

  • Authorization checkbox

Example:

Total: $1,200.00
Due today: $100.00
Remaining balance: $1,100.00
Plan: 11 monthly payments of $100.00
First scheduled payment: May 28, 2026

Require checkbox:

I authorize this organization and GiveHub to charge my selected payment method according to the schedule shown above.

Do not allow submission unless accepted.

 

 

Phase 4: Payment Calculation Logic

Add shared utility functions.

type Frequency = "weekly" | "biweekly" | "monthly" | "quarterly";

function calculatePaymentPlanSchedule(input: {
totalAmountCents: number;
downPaymentAmountCents?: number;
installmentCount: number;
frequency: Frequency;
startDate: Date;
}): {
downPaymentAmountCents: number;
installmentAmountCents: number;
installments: {
installmentNumber: number;
amountCents: number;
dueDate: Date;
}[];
}

Rules:

  • Remaining balance = total - down payment.

  • Split remaining balance across installment count.

  • Handle rounding in cents.

  • Any rounding remainder should be added to the final installment.

  • Do not allow total amount <= 0.

  • Do not allow installment count <= 0.

  • Do not allow down payment greater than total.

  • If pay in full, skip installment generation.

 

 

Phase 5: Form Submission Flow

When public form is submitted:

Case A: Pay in full

  1. Validate form.

  2. Process payment immediately.

  3. Save form submission.

  4. Save contribution/payment.

  5. Send normal receipt.

Case B: Payment plan

  1. Validate form.

  2. Validate payment plan selection.

  3. Tokenize/save payment method using existing saved payment method flow.

  4. If down payment is required, charge down payment immediately.

  5. Create payment_plans record.

  6. Create payment_plan_installments records.

  7. Save authorization acceptance details.

  8. Save form submission.

  9. Send confirmation email with payment schedule.

Important: if down payment charge fails, do not create active payment plan.

 

 

Phase 6: Scheduled Auto-Charging Job

Create backend scheduled job:

processDuePaymentPlanInstallments

Runs daily.

Find installments where:

status = 'scheduled'
AND due_date <= CURRENT_DATE
AND payment_plan.status = 'active'
AND payment_plan.auto_charge = true

For each installment:

  1. Mark installment processing.

  2. Charge saved payment method.

  3. On success:

    • create contribution/payment record

    • mark installment paid

    • link payment_id / contribution_id

    • update payment_plans.next_charge_date

    • if all installments paid, mark plan completed

    • send receipt

  4. On failure:

    • mark installment failed

    • increment retry count

    • store gateway error

    • send failed payment email with update-payment link

    • if max retries exceeded, mark plan failed

Use existing gateway abstraction where possible.

 

Phase 7: Reminder Emails

Create reminder job:

sendPaymentPlanReminders

Runs daily.

Find scheduled installments due in template.reminder_days_before.

Send email:

Subject:

Upcoming payment reminder

Body should include:

  • Org name

  • Plan name

  • Amount

  • Due date

  • Payment method last 4 if available

  • Link to update payment method

 

Phase 8: Failed Payment Recovery

When an installment fails, email the donor.

Subject:

Action needed: payment failed

Include:

  • Plan name

  • Failed amount

  • Due date

  • Reason if safe to show

  • Secure update-payment link

We can reuse the same general saved payment update flow used by pledges/recurring gifts, but make sure links use production FRONTEND_URL, not preview/dev URLs.

 

Phase 9: Admin UI

Add admin pages or sections.

Payment Plans list

Route suggestion:

/org-dashboard/payment-plans

Show:

  • Donor name

  • Email

  • Plan name

  • Total amount

  • Paid amount

  • Remaining amount

  • Status

  • Next charge date

  • Source form

  • Created date

Filters:

  • Active

  • Failed

  • Completed

  • Canceled

  • Form

  • Fund

Payment Plan detail page

Show:

  • Donor info

  • Form submission link

  • Plan summary

  • Authorization accepted timestamp

  • Payment method

  • Installment schedule table

Installment table columns:

  •  

  • Due date

  • Amount

  • Status

  • Paid date

  • Retry count

  • Error

Admin actions:

  • Pause plan

  • Resume plan

  • Cancel plan

  • Retry failed payment

  • Update next charge date

  • Change payment method link / resend update link

 

Phase 10: Reporting

Add payment plan activity to reports.

At minimum:

  • Payment plans created

  • Active plans

  • Failed plans

  • Completed plans

  • Installments collected

  • Future scheduled installments

  • Total outstanding balance

Contribution records from installments should still appear in normal giving/payment reports.

Add metadata to contributions/payments:

source_type: "payment_plan"
source_id: paymentPlanId
installment_id: installmentId
form_submission_id: formSubmissionId

 

Phase 11: Permissions

Add permissions:

payment_plans.view
payment_plans.create
payment_plans.edit
payment_plans.cancel
payment_plans.retry
payment_plan_templates.manage

Admins should have these by default.

 

Phase 12: Audit / Safety

Log key actions:

  • Plan created

  • Authorization accepted

  • Installment charged

  • Installment failed

  • Plan paused

  • Plan resumed

  • Plan canceled

  • Payment method update link sent

  • Manual retry attempted

 

MVP Scope

Build this first:

  1. Payment Plan block in Custom Forms

  2. Pay in full vs payment plan checkout

  3. Card payment method support first

  4. Optional ACH if existing ACH saved-payment flow is already stable

  5. Down payment support

  6. Monthly/weekly/biweekly/quarterly schedules

  7. Daily charge job

  8. Reminder emails

  9. Failed payment email

  10. Admin list/detail view

Do not overbuild initially:

  • No interest

  • No late fees

  • No partial manual payments yet

  • No complex proration

  • No donor self-service cancellation yet

  • No variable installment amounts yet

 

Important implementation notes

Please reuse existing GiveHub payment infrastructure as much as possible.

Do not create a separate payment gateway system.

Use existing:

  • organization payment gateway config

  • saved payment method logic

  • contribution/payment creation

  • receipt email system

  • update-payment method flow

  • fund/category reporting

  • form submission records

The payment plan system should be a layer on top of existing payments, not a replacement.

Critical: make sure public URLs in payment plan emails use the production frontend URL for the environment, not preview/dev URLs.

Givehub.com

GiveHub.com
Acworth, GA 30101

United States
Email: sales@givehub.com
Phone: 866-933-7048

 

MISSION STATEMENT

 

To offer a robust and a superior product suite to help non-profits and churches increase giving and operate more effectively and efficiently with cutting edge technology. 

 

Yours in Christ, 
GiveHub.com Team

  • Facebook Social Icon
  • LinkedIn Social Icon
  • Instagram Social Icon

© 2026 GiveHub.com - All rights reserved - Support - Book a Demo

bottom of page