Data Model Reference
Payment Method#
Stores a donor's saved payment method as a reference to a tokenized record in the payment gateway (e.g. a Stripe PaymentMethod/Customer ID), so the donor can reuse it for future one-time gifts or recurring installments without re-entering card details. Only non-sensitive display data (card brand, last 4 digits, expiry month/year) and gateway reference IDs are stored — the platform never stores the full card number, CVV, or bank account number.
custom · 16 fields · ReadWrite · Donations & Payments
Why it exists#
This is a pure token vault, never a payment record. Stripe.js tokenizes card details in the
donor's browser and only paymentMethod.id ever reaches Apex — the full card number, CVV, and
bank account number never touch this org, so Payment_Method__c only ever stores what's safe
to display back to the donor (Brand__c, Last4__c, expiry) plus the gateway's own reference
IDs. Fingerprint__c lets the app detect when a donor re-saves a card it already has on file.
This object does not relate to Opportunity. A one-time gift stores its own Stripe payment
reference directly on the Opportunity (Stripe_Payment_Intent_Id__c, Payment_Source__c,
Payment_Status__c) and never touches a saved payment method. Payment_Method__c exists
specifically so a recurring schedule (Recurring_Donation__c) can be charged off-session
without the donor re-entering card details each cycle — see Recurring Donation.
Relationships#
| Direction | Field | Related object | Relationship type |
|---|---|---|---|
| → out | Contact__c |
Contact | Lookup |
| → out | Donor_Account__c |
Account | Lookup |
| → out | Payment_Account__c |
Payment Account | Lookup |
| ← in | Payment_Method__c on Recurring Donation |
Recurring Donation | Lookup |
Fields#
| Field | Label | Type | Notes |
|---|---|---|---|
Bank_Name__c |
Bank_Name | Text | Bank name shown for ACH payment methods. Not populated for cards. |
Brand__c |
Brand | Text | Card network (Visa, Mastercard, Amex, Discover). Blank for ACH. |
Contact__c |
Contact | Lookup → Contact |
Owning contact in standard (non-Person Account) orgs. |
Donor_Account__c |
Donor Account | Lookup → Account |
Owning Person Account in Person Account orgs. |
Exp_Month__c |
Exp_Month | Number | Card expiry month, for display only. Blank for ACH. |
Exp_Year__c |
Exp_Year | Number | Card expiry year, for display only. Blank for ACH. |
Fingerprint__c |
Fingerprint | Text | Gateway fingerprint used to detect duplicate payment methods. Docs: PaymentMethodService.saveWalletCard matches on this before inserting; a hit returns the existing row untouched with no DML at all rather than a duplicate, since the guest context all 3 callers run in can never get object-level Edit on this object to update it. |
Is_Default__c |
Is_Default | Checkbox | Checked if this is the donor's default payment method. |
Is_Valid__c |
Is_Valid | Checkbox | Unchecked when the gateway reports this method as no longer usable. Docs: PaymentMethodsSelector.selectByDonor filters WHERE Is_Valid__c = true so an invalid method never surfaces as a choice in the donor-facing picker, while selectByDonorSystem (used by the guest/portal write paths) reads all rows so Fingerprint__c dedup still finds them. |
Last4__c |
Last4 | Text | Last 4 digits shown to identify the method. Never the full card/account number. |
Payment_Account__c |
Payment Account | Lookup → Payment_Account__c |
Which of your configured Stripe accounts holds this saved card. Set automatically when the donor saves the card and must not be changed by hand — the card only works with the account listed here. Docs: Added 2026-08-07 to close a live renewal bug. Before this field the renewal batch re-derived the Stripe account from Campaign.Payment_Account__c on every run, so re-pointing a campaign at a second Stripe account made every in-flight schedule charge account-A tokens against account B; Stripe answers resource_missing and the batch fed that into the dunning cadence as though the donor's card had declined. deleteConstraint is deliberately omitted (SetNull), matching Opportunity.Payment_Account__c and Donation_Staging__c.Payment_Account__c: this is a historical transactional reference, not a config link. The hard block on deleting an in-use account lives on Campaign.Payment_Account__c (Restrict) plus SetupController.DeletePaymentAccount's friendly guard, and every wallet row is reachable through a campaign, so nothing is lost by not repeating it here. |
Removed_Date__c |
Removed Date | DateTime | The date and time the donor removed this card themselves from their online account. Blank means the card is still available to them. Useful when a donor asks why a card stopped being offered, or when reconciling a gift that was charged shortly before the card was removed. Docs: Companion to Removed__c — set in the same SYSTEM_MODE update so the two never disagree. Nothing queries on it; it exists so support can answer "when did this card disappear from my account?" without reading field history, which is not enabled on this object. |
Removed__c |
Removed | Checkbox | Ticked when the donor removed this card themselves from their online account. The card stays on file so past gifts and any refunds still trace back to it, but it is no longer offered when someone gives again or when a recurring gift is set up. Do not tick or untick this yourself — ask the donor to remove or re-add the card from their donor portal instead. Docs: A soft remove, for three reasons: the Guest License can never hold object Delete; historic Donation__c rows need Stripe_Payment_Method_Id__c to stay resolvable for refund traceability; and DonorPortalService.removeWalletCard refuses removal outright while an Active, Paused or Failed Recurring_Donation__c still points at the card, so a removed row can never be one a live schedule depends on (Failed blocks too — a dunning donor who removed the card would have nothing left to fix). Removal makes NO Stripe detach callout — the site-context guard added to the gateway refuses back-office Stripe calls from a site context by design, and the donor's click path must not carry a callout. PaymentMethodsSelector filters Removed__c = true out of the two wallet-listing methods (selectValidByDonor, selectValidByDonorSystem). selectByDonorSystem is deliberately NOT filtered — it is the dedup query, and Stripe_Payment_Method_Id__c is unique, so hiding a removed row there would make re-adding the same card die on the uniqueness constraint instead of reinstating it. selectById / selectDetailById stay unfiltered so back-office lookups still resolve historic rows. |
Stripe_Customer_Id__c |
Stripe Customer ID | Text | Stripe Customer reference ID (cus_...). Not a secret. |
Stripe_Payment_Method_Id__c |
Stripe Payment Method ID | Text | Stripe PaymentMethod reference ID (pm_...). Not a secret. Docs: The billing-critical copy of this id actually lives on Recurring_Donation__c, stamped directly by the caller of PaymentMethodService.saveWalletCard — this wallet-row copy is display/dedup metadata only, which is what makes the insert-only (never-updated) row safe under the guest-context restriction. |
Type__c |
Type | Picklist | Card or US Bank Account (ACH). |