Skip to content

Data Model Reference

Recurring Donation#

Represents a donor's ongoing recurring giving schedule (subscription profile) supporting a campaign. This package self-manages the billing schedule: a daily scheduler charges each installment off-session against a saved Stripe payment method, so there is no native Stripe subscription object involved. Related to the donor via the dual Contact/Person-Account lookup pattern and to Designation__c/Recurring_Designation__c for fund allocation; each state change or payment attempt is logged to a child Recurring_Donation_History__c record.

custom · 39 fields · ReadWrite · Recurring Giving

Why it exists#

Stripe Billing/Subscriptions is deliberately not used here — instead a daily batch job walks due Recurring_Donation__c records and charges each installment off-session against the donor's saved Payment_Method__c, inserting a normal Opportunity per installment via Recurring_Donation__c (so every past charge is still a first-class, reportable donation, not a line on an external subscription object). Every state change or charge attempt — success, decline, retry — is appended to a child Recurring_Donation_History__c record, which is what powers dunning and renewal reporting without needing to replay Stripe's event log.

Fund allocation is split by design/reuse: Designation__c sets the default allocation for the schedule, while individual installments can override it via Recurring_Designation__c.

Relationships#

Direction Field Related object Relationship type
→ out Campaign__c Campaign Lookup
→ out Contact__c Contact Lookup
→ out Designation__c Designation Lookup
→ out Donor_Account__c Account Lookup
→ out Payment_Account__c Payment Account Lookup
→ out Payment_Method__c Payment Method Lookup
← in Recurring_Donation__c on Opportunity Opportunity Lookup
← in Recurring_Donation__c on Recurring Designation Recurring Designation MasterDetail
← in Recurring_Donation__c on Recurring Donation History Recurring Donation History MasterDetail
← in Recurring_Donation__c on Transaction Log Transaction Log Lookup

Fields#

Field Label Type Notes
Amount__c Amount Currency The amount charged to the donor's card each billing cycle.
Campaign__c Campaign Lookup → Campaign The campaign this recurring gift supports.
Docs: Also the field CancelGoalReachedRecurringQueueable filters on when a campaign's goal is newly crossed with Recurring_Behaviour_At_Goal__c set to Cancel — every Active schedule pointing at that campaign is a candidate for bulk auto-cancellation, chunked at 200 with Transaction Finalizer retry.
Cancellation_Reason__c Cancellation Reason Text Why this recurring donation was cancelled, if known.
Docs: Also populated by system-initiated cancellations, not just donor/admin ones — CancelGoalReachedRecurringQueueable calls RecurringDonationService.cancelSubscriptions() with a fixed reason string when Campaign.Recurring_Behaviour_At_Goal__c is Cancel and the campaign's goal is newly crossed, so a blank-looking manual cancel and an automatic goal-triggered one are both represented here, distinguishable only by the reason text.
Contact__c Contact Lookup → Contact The donor for this recurring gift (standard Account orgs).
Currency_ISO_Code__c Currency ISO Code Text Three-letter currency code for this recurring gift, e.g. USD.
Designation__c Designation Lookup → Designation__c The fund or program this recurring gift supports, if not split across multiple funds.
Donor_Account__c Donor Account Lookup → Account The donor for this recurring gift (Person Account orgs).
Donor_Covered_Fees__c Donor Covered Fees Checkbox Checked when the donor agreed to cover the processing fee. Each renewal then charges their gift amount plus the current fee, so the amount taken is slightly more than the Amount field. Uncheck it to bill only the gift amount from the next renewal onward.
Docs: Amount__c on a schedule is always the GIFT, never the grossed-up charge — this flag is what tells RecurringRenewalBatch to add a fee on top at charge time. Storing the gross in Amount__c instead would have been simpler but wrong twice over: the designation splits that every installment inherits would allocate the fee to a fund, and the fee would freeze at whatever the rate was on signup day, so a later Settings__c rate change would never reach existing donors. Recomputing per cycle from FeeCoverageService.computeFee also means the donor's coverage does not silently lapse after month 1, which is what happens if the fee is only ever applied to the signup charge.
End_Date__c End Date Date The date after which this recurring gift should stop being charged. Leave blank for a gift that continues until the donor stops it.
Docs: Unlike Installment_Cap__c (enforced after a charge), this one is enforced BEFORE any charge: it is part of RecurringDonationsSelector.dueCondition(), so a schedule past its end date is never even selected, and RecurringRenewalBatch.isStillDue() re-checks it against a freshly read row. Nothing marks such a schedule Completed on the day it expires — it simply stops being due, and processRenewal marks it Completed the next time it would otherwise have renewed.
Failed_At__c Failed At DateTime When this recurring gift stopped retrying and moved to Failed. Used to auto-cancel it after the configured grace period — do not edit directly.
Failed_Payment_Count__c Failed Payment Count Number Number of renewal charges in a row that have failed.
Frequency__c Frequency Picklist How often the donor is charged for this recurring gift.
Installment_Cap__c Installment Cap Number Set this to end the gift automatically after a fixed number of payments — enter 12 for a "$50 a month for a year" pledge. Leave blank for a gift that continues until the donor stops it.
Docs: The cap is enforced AFTER a charge, not before one: RecurringDonationService.processRenewal increments Installments_Paid__c on a successful capture and, when the incremented count reaches this cap, flips Status__c to Completed and clears Next_Payment_Date__c. That is why the due-selector needs no field-to-field comparison (SOQL cannot express one) — a completed schedule simply stops matching the Status__c = 'Active' predicate. A cap lowered below the count already paid therefore ends the schedule at the NEXT successful charge, not retroactively.
Installments_Paid__c Installments Paid Number A running count of payments successfully collected on this recurring gift. The system maintains this — do not edit it.
Docs: Maintained by Apex, not a roll-up summary: Recurring_Donation__c ↔ Opportunity is a lookup, not a master-detail, so the platform cannot roll up the installment count. Gift #1 is counted at signup (RecurringDonationService.createScheduleFromStaging sets it to 1) and each later successful renewal increments it in processRenewal's success branch. A schedule created before this field existed starts at 0 and counts forward from its next renewal, so a cap applied to a long-running legacy schedule grants it a full fresh term — deliberate, since the historical count cannot be reconstructed for schedules whose installments predate the field.
Last_Dunning_Email_Sent_At__c Last Dunning Email Sent At DateTime When a payment-failure email was last sent to the donor.
Last_Error__c Last Error LongTextArea The last error encountered trying to charge this recurring gift.
Last_Payment_Amount__c Last Payment Amount Currency The amount of the most recent successful payment.
Last_Payment_Date__c Last Payment Date Date The date the most recent payment was successfully charged.
Mandate_Accepted__c Mandate Accepted Checkbox Whether the donor has agreed to the recurring-charge authorization.
Docs: Actively enforced, not just a record of consent — RecurringDonationsSelector's active-batch query requires Mandate_Accepted__c = true, so a schedule created without it set is silently excluded from every renewal charge, not just flagged for audit.
Mandate_Timestamp__c Mandate Timestamp DateTime When the donor agreed to the recurring-charge authorization.
Mandate_Version__c Mandate Version Text Which version of the authorization terms the donor accepted.
NPSP_Recurring_Donation_Id__c NPSP Recurring Donation Id Text Internal reference to the mirrored NPSP recurring donation record; not for manual entry.
Docs: Part of the optional NPSP extension, detected at runtime via Schema.describeSObjects() rather than a hard package dependency — the core package works whether or not NPSP is installed. When present, this schedule maps to npe03__Recurring_Donation__c as a mirror, not a replacement; Designation__c/Recurring_Designation__c map to NPSP's GAU allocations separately.
Next_Payment_Date__c Next Payment Date Date The next date this recurring gift is scheduled to be charged.
Docs: Frozen on the missed date once a charge declines, not advanced day to day. A decline sets Next_Retry_Date__c and leaves this field alone; RecurringDonationsSelector's due-schedule query is gated so the normal branch only fires when Next_Retry_Date__c is null, otherwise a stale Next_Payment_Date__c would re-select the schedule every run and defeat the dunning cadence. Only a successful charge advances this field, via advanceScheduleDate.
Next_Retry_Date__c Next Retry Date Date The next date a failed payment will automatically be retried.
Docs: Computed as today plus SettingsService.retryIntervalDays(), the same flat interval for every attempt — see the doc note on Retry_Attempt__c about the stale fixed 1/3/5-day cadence described elsewhere.
Paused_Until__c Paused Until Date The date this recurring gift will automatically resume, if paused.
Payment_Account__c Payment Account Lookup → Payment_Account__c Which of your configured Stripe accounts renewals for this schedule are charged to. Set automatically when the schedule starts — changing the fundraiser's payment account later does not, and must not, move an existing schedule.
Docs: Second rung of the renewal resolution chain: Payment_Method__r.Payment_Account__c, then this field, then Campaign.Payment_Account__c for rows predating both (see RecurringRenewalBatch). Stamped from Donation_Staging__c.Payment_Account__c, which is already in hand at RecurringDonationService.createScheduleFromStaging — the account was decided at intent time, so this is a snapshot of a known fact rather than a fresh resolution. deleteConstraint omitted (SetNull) for the same reason as Payment_Method__c.Payment_Account__c.
Payment_Method__c Payment Method Lookup → Payment_Method__c The saved payment method record associated with this recurring gift, if tracked.
Docs: Kept in sync automatically, not just set once at signup. RecurringDonationService. updatePaymentMethod calls PaymentMethodService.saveWalletCard and registers the returned Payment_Method__c on this schedule via uow.registerRelationship, so a card swap updates both this lookup and Stripe_Payment_Method_Id__c together in the same unit of work.
Re_Auth_Link_Sent_At__c Re-Auth Link Sent At DateTime When a re-authentication email was last sent to the donor.
Re_Auth_Required__c Re-Auth Required Checkbox Whether the donor needs to re-authenticate their card before the next charge can go through.
Docs: Set by RecurringDonationService.processRenewal's requiresAction branch and excluded from the renewal batch's selection query (RecurringDonationsSelector) alongside Mandate_Accepted__c and Status__c — a schedule needing re-auth is simply skipped by the batch, not retried, until a donor completes 3-D Secure and this is cleared.
Retry_Attempt__c Retry Attempt Number How many retry attempts have been made after a failed payment.
Docs: The description above states a fixed 1/3/5-day retry schedule; that is stale. The current implementation in RecurringDonationService.processRenewal uses a flat interval read from SettingsService.retryIntervalDays(), capped by SettingsService.maxFailedPaymentRetries(), not a hardcoded per-attempt day count. Trust the Apex over this description until it is corrected.
Status__c Status Picklist The current state of this recurring gift.
Docs: Pending is the picklist's UI default, but RecurringDonations.onBeforeInsert (setDefaults) assigns Active to any record inserted with a null Status__c, so a schedule created through normal Apex flows practically never lands on Pending — that value only shows up if something explicitly sets it.
Stripe_Customer_Id__c Stripe Customer Id Text Internal Stripe reference for this donor's saved payment method; not for manual entry.
Stripe_Payment_Method_Id__c Stripe Payment Method Id Text Internal Stripe reference for the saved card charged each cycle; not for manual entry.
URL_Parameters__c URL Parameters LongTextArea The full set of tracking parameters from the link the donor clicked, stored as JSON. Reference only — report on the UTM fields instead.
Docs: Set once at signup by RecurringDonationService from Donation_Staging__c.Options_JSON__c -> attribution, and is the template every renewal Opportunity copies its UTM fields from. Deliberately never refreshed — it records how the donor was acquired, not how they last interacted.
UTM_Campaign__c UTM Campaign Text The marketing appeal name from the link, e.g. year-end-2026. This is the marketer's label, not the Salesforce Campaign — filled in automatically.
Docs: Set once at signup by RecurringDonationService from Donation_Staging__c.Options_JSON__c -> attribution, and is the template every renewal Opportunity copies its UTM fields from. Deliberately never refreshed — it records how the donor was acquired, not how they last interacted.
UTM_Content__c UTM Content Text Which link or creative variant was clicked, for A/B tests. Filled in automatically — usually blank.
Docs: Set once at signup by RecurringDonationService from Donation_Staging__c.Options_JSON__c -> attribution, and is the template every renewal Opportunity copies its UTM fields from. Deliberately never refreshed — it records how the donor was acquired, not how they last interacted.
UTM_Medium__c UTM Medium Text The channel that carried the link, e.g. email, cpc or social. Filled in automatically — leave it alone.
Docs: Set once at signup by RecurringDonationService from Donation_Staging__c.Options_JSON__c -> attribution, and is the template every renewal Opportunity copies its UTM fields from. Deliberately never refreshed — it records how the donor was acquired, not how they last interacted.
UTM_Source__c UTM Source Text Where this gift came from, e.g. newsletter or facebook. Filled in automatically from the link the donor clicked — leave it alone.
Docs: Set once at signup by RecurringDonationService from Donation_Staging__c.Options_JSON__c -> attribution, and is the template every renewal Opportunity copies its UTM fields from. Deliberately never refreshed — it records how the donor was acquired, not how they last interacted.
UTM_Term__c UTM Term Text The paid-search keyword that led here, if any. Filled in automatically — usually blank.
Docs: Set once at signup by RecurringDonationService from Donation_Staging__c.Options_JSON__c -> attribution, and is the template every renewal Opportunity copies its UTM fields from. Deliberately never refreshed — it records how the donor was acquired, not how they last interacted.

← Back to the diagram