Skip to content

Data Model Reference

Opportunity#

The donation itself — a customized standard Opportunity carrying billing details, the Stripe payment trail, and refund/dispute state for a single gift.

standard · 49 fields · Donations & Payments

Why it exists#

There's no dedicated "Donation" object — every gift, one-time or a single recurring installment, is a standard Opportunity extended with 41 custom fields. That keeps donations inside Salesforce's native pipeline, reporting, and forecasting tooling instead of reinventing it, and it's why the object carries so much: donor billing address, tribute/dedication details, company-match flags, the full Stripe payment/refund/dispute trail (Stripe_Payment_Intent_Id__c, Payment_Status__c, Dispute_*__c), and fee-coverage math.

A recurring installment links back to its schedule via Recurring_Donation__c. Note what's not here — see the Payment Method reference for why Payment_Method__c isn't one of this object's relationships.

Relationships#

Direction Field Related object Relationship type
→ out Payment_Account__c Payment Account Lookup
→ out Recurring_Donation__c Recurring Donation Lookup
→ out Soft_Credit_Contact__c Contact Lookup
→ out Soft_Credit_Donor_Account__c Account Lookup
→ out Soft_Credit_Household_Account__c Account Lookup
← in Opportunity__c on Custom Answer Custom Answer MasterDetail
← in Opportunity__c on Donation Designation Donation Designation MasterDetail
← in Opportunity__c on Donation Staging Donation Staging Lookup
← in Opportunity__c on Event Attendee Event Attendee Lookup
← in Opportunity__c on Guest Request Guest Request Lookup
← in Opportunity__c on Transaction Log Transaction Log Lookup

Fields#

Field Label Type Notes
Balance_Transaction_Id__c Balance Transaction ID Text The processor's reference for this gift's settlement line. Set automatically; use it to find the transaction in your payment dashboard.
Docs: No caseSensitive setting: the platform only accepts it alongside unique=true, and uniqueness is deliberately off here (see below). Processor ids are case-sensitive in practice, so match on them exactly in any code that reads this field rather than assuming the index will do it. External ID but deliberately NOT unique, unlike Stripe_Payment_Intent_Id__c. That field's unique index is the idempotency backstop that stops two Opportunities being created for one payment; this one guards nothing, and a unique index here would turn any future re-stamp or backfill into a deploy-breaking constraint for no benefit. This is the join key a payout feature would need: the processor's balance transaction has no payout field of its own, so tying a gift to a bank deposit means listing the payout's balance transactions and matching on this id. Capturing it now is what keeps that feature a read of existing data rather than a re-fetch of every historical charge.
Check_Number__c Check Number Text Check number for this donation, if paid by check.
Company_Match_Amount__c Company Match Amount Currency The amount the employer sent to match this gift. Enter it when the match is received — record the employer's payment itself as its own donation, since this field is a reference figure only and is not counted in campaign totals.
Docs: No Apex writes this field — it exists purely for staff entry and reporting. It is excluded from every rollup on purpose (see Opportunities domain / CampaignRollupBatch, which sum Amount only). If a future release automates match intake, the employer's gift should still land as its own Opportunity and this field should stay a reference.
Company_Match_Requested_On__c Company Match Requested On DateTime When staff were first asked to chase this employer match. Set automatically — leave it alone; clearing it would cause a duplicate follow-up task on the next automation run.
Docs: The idempotency guard for CompanyMatchService.createFollowUps. Platform events are at-least-once, so DonationFinalized__e can be redelivered; the service only picks up Opportunities where this is null, and stamps it in the same UnitOfWork that registers the Task. Also doubles as the "how long has this match been sitting" clock for reporting, which is why it is a DateTime rather than a bare checkbox.
Company_Match_Status__c Company Match Status Picklist How far along the employer match is. Move it to Requested once the paperwork is filed with the employer, then Received when their gift arrives (record the amount in Company Match Amount) or Declined if they turn it down.
Docs: Stamped 'Pending' by DonationService.applyTributeAndMatch at finalize, at the same moment Company_Match__c is set — the two always move together, so a match flagged with no status means the row predates this field. Everything after Pending is a human judgement call and is left to staff; no Apex ever advances it. The "Pending Company Matches" list view and CompanyMatchService both key off Pending, so leaving a fulfilled match at Pending is what makes it keep showing up as outstanding work.
Company_Match__c Company Match Checkbox Check if the donor said their employer will match this gift.
Company_Name__c Company Name Text Donor's employer name, for company-matched gifts.
Currency_ISO_Code__c Currency ISO Code Text Three-letter currency code this gift was charged in, e.g. USD.
Docs: A plain custom Text field, not the platform's native CurrencyIsoCode — do not confuse the two. This exists because the package deliberately does not enable Salesforce multi-currency; an org that turns on native multi-currency separately is outside this package's tested scope.
Dispute_Amount__c Dispute Amount Currency Amount Stripe reports as disputed. Managed automatically — do not edit.
Dispute_Closed_Date__c Dispute Closed Date Date Date the dispute on this gift was resolved. Managed automatically.
Dispute_Evidence_Due_Date__c Dispute Evidence Due Date Date Deadline to respond to this dispute with evidence.
Dispute_Id__c Dispute ID Text Stripe's identifier for the dispute on this gift. Managed automatically.
Docs: "The reconciler" referenced in the description is RefundReconciliationBatch — the scheduled batch that polls Stripe for dispute state and writes this and the sibling Dispute_ fields; there is no dispute-specific webhook handler in StripeEventProcessor.*
Dispute_Opened_Date__c Dispute Opened Date Date Date this dispute was opened with Stripe.
Dispute_Reason__c Dispute Reason Text Reason the cardholder gave for disputing this charge.
Dispute_Status__c Dispute Status Picklist Current status of the dispute with Stripe, if any.
Donation_Type__c Donation Type Picklist What kind of donation or purchase this record represents.
Docs: Recurring_Installment is set once per charge by RecurringDonationService.applyRenewalOutcome() each time RecurringRenewalBatch runs — every installment is its own Opportunity linked back via Recurring_Donation__c, there is no single Opportunity that represents the whole schedule.
Donor_Covered_Fees__c Donor Covered Fees Checkbox Checked when the donor ticked "cover the processing fee" at checkout, so the amount on this gift includes that fee. Report on it to see how many of your donors opt in.
Docs: The take-up rate for fee coverage is the number an admin actually wants, and Fee_Amount__c > 0 cannot supply it: a gift small enough that the computed fee rounds to zero would look like a decline. Written alongside Fee_Amount__c by DonationService.processOneTimeStagingRecord and RecurringDonationService.processRenewal, from Donation_Staging__c.Fee_Amount__c and Recurring_Donation__c.Donor_Covered_Fees__c respectively. Event orders never set it — fee coverage is not offered at ticket checkout.
Event_Amount__c Event Amount Currency Portion of this gift that is not tax-deductible (e.g. the value of an event ticket).
Event_Deductible_Amount__c Event Deductible Amount Currency Tax-deductible portion of an event ticket order, for receipts.
Fee_Amount__c Fee Amount Currency Processing fee amount added by the donor, if fee coverage was selected. Included in the total Amount charged.
Docs: Written at finalize by DonationService.processOneTimeStagingRecord and RecurringDonationService.processRenewal from the value FeeCoverageService.computeFee() produced when the PaymentIntent was created; EventPurchaseService always writes 0 (ticket orders don't offer fee coverage). The gross-up is true gross-up, not gift × percent — the processor charges its percentage on the total it captures, so a $100 gift under 2.9% + $0.30 carries a $3.30 fee, not $3.20. See FeeCoverageService for the formula; it is the only place it exists. Reporting note: this is a component of Amount, not an addition to it. "Gift revenue" is Amount − Fee_Amount__c; Amount is what the card was charged. Not the same thing as the Stripe fee the org itself absorbs on a donation where the donor declined to cover it, which never appears on the Opportunity at all.
Funds_Available_On__c Funds Available On Date When this gift's money becomes available to pay out to your bank. Set automatically by the processor; it is normally a few days after the gift date.
Docs: Written once at finalize by SettlementStampHelper.applyTo from the balance transaction's available_on, which is an epoch-SECONDS timestamp converted in StripeGateway.applySettlement and read in GMT. GMT is deliberate: the processor states availability against its own clock, and re-reading it in the running user's time zone would shift the date by a day for anyone west of UTC without making it any more true. This is the availability date, not the payout date. Funds become available on this date; when they actually leave for the bank depends on the org's payout schedule, which the package does not read.
Net_Donation_Amount__c Net Donation Amount Currency Tax-deductible amount of this gift, shown on the donor's receipt.
Docs: Written at finalize by DonationService.processOneTimeStagingRecord, EventPurchaseService. processEventStagingRecord, and RecurringDonationService.processRenewal. A donor-covered processing fee (Fee_Amount__c) is deliberately NOT subtracted here. The charity received the whole charge and then paid its own processing cost out of it — the donor got nothing back — so the full amount stays deductible and subtracting the fee would under-report the deduction on every receipt that covers one. Only a genuine quid pro quo (the ticket fair-market value on event orders) reduces this figure. Do not "fix" the apparent inconsistency with Fee_Amount__c by subtracting it. ReceiptService still computes the deductible split itself from Event_Amount__c/ Event_Deductible_Amount__c rather than reading this field; the two agree by construction. Its only other consumer is lexOpportunityBreakdown.js (read-only display); remains editable on the Donation/Pledge/Grant layouts for manual correction.
Net_Settled_Amount__c Net Settled Amount Currency What your organization actually received for this gift after the processor's fee. Use this figure, not Amount, when reconciling against your bank statement.
Docs: Written once at finalize by SettlementStampHelper.applyTo, alongside Processor_Fee__c and from the same balance transaction. Taken from the processor's own net rather than computed as Amount − Processor_Fee__c: on a cross-currency charge the two are denominated in different currencies and the subtraction would silently produce a number that is not money in either of them. Not to be confused with Net_Donation_Amount__c despite the similar name — that is the TAX-DEDUCTIBLE portion (Amount less any goods or services the donor received) and exists for the receipt. This one is a CASH figure and exists for finance. They answer different questions and will usually disagree; neither is a correction of the other.
Payment_Account__c Payment Account Lookup → Payment_Account__c The configured payment account this gift was charged against.
Docs: Matters most in orgs with more than one Payment_Account__c (an org may configure several Stripe accounts and route each campaign to one via Campaign.Payment_Account__c) — a refund or dispute lookup that ignores this field and assumes a single default account will hit the wrong Stripe account's API keys.
Payment_Source__c Payment Source Picklist Payment method the donor used for this gift.
Payment_Status__c Payment Status Picklist Current payment state of this gift. Managed automatically by the payment integration.
Processor_Fee__c Processor Fee Currency The payment processor's cut of this charge, which your organization pays. Set automatically from the payment record; blank if the processor had not posted it yet.
Docs: Written once at finalize by SettlementStampHelper.applyTo, from the balance transaction expanded onto the PaymentIntent the reconciler was already fetching (see StripeGateway.applySettlement) — no extra callout on any payment path. Blank vs zero is load-bearing. The processor posts its balance transaction when the charge settles, so a gift finalized in the same instant the charge succeeded, or an ACH debit still processing, legitimately has no figure yet. Writing 0 in that case would report a free charge, so the helper leaves the field alone instead. Never treat blank as 0 in a rollup or a report — filter it out. Do NOT confuse with Fee_Amount__c: that is money the DONOR added on top and is a component of Amount; this is money the PROCESSOR removed and is not in Amount at all. A single gift can carry both, and the two are not expected to be equal even when the donor covered the fee — the gross-up is computed from the org's configured rates, the actual fee from what the processor really charged for that specific card.
Receipt_Number__c Receipt Number Text The unique number on this donor's tax receipt. Filled in automatically — when the payment succeeds for an online gift, or as soon as you mark a cheque or cash gift Closed Won. Do not type or change it, because the donor may already have a receipt quoting this number.
Docs: Case-insensitive uniqueness (caseSensitive is deliberately omitted, unlike Stripe_Payment_Intent_Id__c which sets it true). The package always generates upper-case numbers, but the prefix comes from an admin-editable setting, and "rcpt-2026-000001" must not be issuable alongside "RCPT-2026-000001" — a donor and an auditor would read those as the same receipt. Case-insensitive is the stricter choice for uniqueness, so it is the one a compliance field should take. This field replaced a fake receipt number that the guest receipt LWC derived from Receipt_Token__c (its last 8 characters). That was wrong twice over: it was not a serial number in any sense, and Receipt_Token__c is the bearer credential that authorizes access to the receipt, so the old label printed part of a secret onto a document donors email, print and hand to an accountant. Never source a user-visible reference from Receipt_Token__c. Two write paths, not one (the second added 2026-08-07, gap-audit §3.3). The three online paths — DonationService, EventPurchaseService, RecurringDonationService — stamp the number on the same Unit of Work that finalizes the gift. Everything else is numbered by Opportunities.stampOfflineReceiptNumber in the BEFORE trigger, which covers the cheque and cash gifts staff key in by hand; those used to print "—" on the year-end statement beside online gifts carrying a real serial, which made the org's numbering look holed. The trigger skips a gift that already has a number, so the online paths always win and no gift is ever numbered twice. Pledge record types are excluded — a Pledge is unpaid by definition. Deliberately NOT an Auto Number field. Auto Number stamps every Opportunity at insert, including pledges, abandoned checkouts and gifts that never complete, so the receipt series would be mostly holes; its format is frozen once packaged; and it cannot restart each tax year. See ReceiptNumberService for the counter that replaces it.
Receipt_Token__c Receipt Token Text Internal security token for the public receipt link. Do not share or expose this value.
Recurring_Donation__c Recurring Donation Lookup → Recurring_Donation__c The recurring donation schedule this installment belongs to, if any.
Docs: Set by RecurringDonationService.applyRenewalOutcome() when the daily RecurringRenewalBatch charges an installment off-session against the parent Recurring_Donation__c's stored Stripe_Customer_Id__c/Stripe_Payment_Method_Id__c. There is no Stripe Subscription object behind this relationship — the schedule is entirely self-managed on this package's side.
Refund_Date__c Refund Date Date Date this gift was last refunded, if applicable.
Refund_Reason__c Refund Reason LongTextArea Reason given for the most recent refund on this gift.
Refunded_Amount__c Refunded Amount Currency Total amount refunded to the donor so far. Managed automatically — never edit directly.
Soft_Credit_Contact__c Soft Credit Contact Lookup → Contact The person who should get credit for this gift even though someone else paid for it. Their giving history and recognition reports will include this gift; the gift itself still belongs to the donor who paid.
Docs: One of the standard Person Account dual-field pairs — this field and Soft_Credit_Donor_Account__c hold the SAME individual, populated according to which account model the org runs. Soft_Credit_Household_Account__c is deliberately NOT part of that pair: it holds a different party (the donor's household), so a tribute honoree and a household credit can sit on one gift without competing for the slot. Written by SoftCreditService off the DonationFinalized__e subscriber, and by staff directly on the record. Automation only ever fills it when it is blank — see Soft_Credit_Source__c for how a hand-entered value is protected.
Soft_Credit_Donor_Account__c Soft Credit Donor Account Lookup → Account The person who should get credit for this gift even though someone else paid for it. Use this field if your org uses Person Accounts; otherwise use Soft Credit Contact.
Docs: The Person Account half of the package-wide dual-lookup rule. Never populated at the same time as Soft_Credit_Contact__c — they are two spellings of one person, and which one is used is decided by DonorResolutionService.isPersonAccountOrg(), never by branching on IsPersonAccount at the call site. This is NOT the household field. Soft_Credit_Household_Account__c holds a different party and may be populated alongside either half of this pair.
Soft_Credit_Household_Account__c Soft Credit Household Lookup → Account The household or organization that should share credit for this gift alongside the individual donor. Leave blank if the gift is already recorded against that household.
Docs: Deliberately a THIRD field rather than a third value of the dual pair above. The pair (Soft_Credit_Contact__c / Soft_Credit_Donor_Account__c) holds one individual in two org shapes; this holds a different party entirely. Keeping them separate is what lets a tribute honoree and a household credit coexist on one gift instead of overwriting each other. Filled by SoftCreditService from the donor Contact's AccountId, and ONLY when that account differs from the donation's own AccountId — in the common household-account org the two are the same and this field would be a duplicate of a value already on the record.
Soft_Credit_Source__c Soft Credit Source Picklist Shows whether the soft credit was worked out from the gift's tribute details or entered by hand. Anything marked Manual is left alone by automation.
Docs: The provenance marker that makes "manual beats automation" enforceable. SoftCreditService writes 'Tribute Honoree' in the same save as the lookup it resolved; the Opportunities domain stamps 'Manual' when a soft-credit lookup changes without the source changing alongside it, which is exactly the shape of a person editing the record by hand. It describes the individual pair only. Soft_Credit_Household_Account__c has one write path (SoftCreditService) and so needs no provenance of its own.
Stripe_Payment_Intent_Id__c Stripe Payment Intent ID Text Stripe's identifier for the payment behind this gift. Set automatically.
Tribute_Ecard_Requested__c Tribute Ecard Requested Checkbox Leave unchecked unless the donor explicitly asked you to notify the honoree or their family. Unchecked means no e-card is ever sent, even if a notification email address is filled in — so untick this rather than clearing the address when a donor changes their mind.
Docs: Added as a plain record of the donor's choice, but nothing read it until the 2026-08-07 gap audit (docs/gap-audit-2026-08-07.md, finding 1.1) found the e-card sending on the presence of Tribute_Notification_Email__c alone. Two writers keep it honest: DonationService.applyTributeAndMatch maps the form's tributeEcardRequested onto it, and dfForm clears the address whenever the box is unticked.
Tribute_Message__c Tribute Message LongTextArea Personal message to include in the tribute notification, if any.
Tribute_Name__c Tribute Name Text Name of the person this gift honors or memorializes.
Tribute_Notification_Email__c Tribute Notification Email Email Email address to notify about this tribute gift, if applicable.
Tribute_Type__c Tribute Type Picklist Select whether this gift is a tribute, and of which kind.
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 by DonationService.applyAttribution() (one-time and event checkout) and copied onto every renewal installment by RecurringDonationService, so an installment carries the attribution of the signup that created it rather than of the night the renewal ran. Source is Donation_Staging__c.Options_JSON__c -> attribution, normalized by AttributionCapture.
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 by DonationService.applyAttribution() (one-time and event checkout) and copied onto every renewal installment by RecurringDonationService, so an installment carries the attribution of the signup that created it rather than of the night the renewal ran. Source is Donation_Staging__c.Options_JSON__c -> attribution, normalized by AttributionCapture.
UTM_Content__c UTM Content Text Which link or creative variant was clicked, for A/B tests. Filled in automatically — usually blank.
Docs: Set by DonationService.applyAttribution() (one-time and event checkout) and copied onto every renewal installment by RecurringDonationService, so an installment carries the attribution of the signup that created it rather than of the night the renewal ran. Source is Donation_Staging__c.Options_JSON__c -> attribution, normalized by AttributionCapture.
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 by DonationService.applyAttribution() (one-time and event checkout) and copied onto every renewal installment by RecurringDonationService, so an installment carries the attribution of the signup that created it rather than of the night the renewal ran. Source is Donation_Staging__c.Options_JSON__c -> attribution, normalized by AttributionCapture.
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 by DonationService.applyAttribution() (one-time and event checkout) and copied onto every renewal installment by RecurringDonationService, so an installment carries the attribution of the signup that created it rather than of the night the renewal ran. Source is Donation_Staging__c.Options_JSON__c -> attribution, normalized by AttributionCapture.
UTM_Term__c UTM Term Text The paid-search keyword that led here, if any. Filled in automatically — usually blank.
Docs: Set by DonationService.applyAttribution() (one-time and event checkout) and copied onto every renewal installment by RecurringDonationService, so an installment carries the attribution of the signup that created it rather than of the night the renewal ran. Source is Donation_Staging__c.Options_JSON__c -> attribution, normalized by AttributionCapture.

← Back to the diagram