Skip to content

Core Concepts

Recurring giving#

A recurring gift is not a Stripe Subscription. Salesforce is the system of record for the billing schedule, and a scheduled job drives each renewal charge directly.

Why self-managed billing#

Stripe's own Subscriptions product would work, but it means two systems can disagree about when a donor is billed, what happens on a failed charge, or when a subscription should lapse — and reconciling that disagreement means either polling Stripe constantly or standing up a webhook. Pledgivo instead treats Recurring_Donation__c as the single source of truth for the billing schedule, and charges the donor's saved card off-session (no browser involved) on that schedule.

How the donor picks a frequency#

The donation form's frequency block is a segmented control — One-time, Monthly, Annually — with a sliding marker showing the current choice. Directly beneath it, a line restates the choice in the donor's own numbers: pick $40 and switch to Monthly and it reads $40 every month — $480 over a year. Cancel anytime.

That annualised figure is the point of the block. The commitment a donor is actually weighing on a recurring gift is the yearly one, and asking them to work it out in their head is the moment most of them fall back to a single gift. Monthly also carries a Most popular badge; recurring donors are worth several times a one-time donor over their lifetime, so that option gets a visible reason rather than sitting as one of three equal-weight tabs.

Nothing here is configurable per campaign beyond the block's title, help text, and the three option labels (Settings → Experience Cloud → Designs). The readout wording and the badge are fixed.

Saved payment methods#

When a donor sets up a recurring gift, Stripe tokenizes their card into a reusable payment method, which Salesforce stores a reference to on Payment_Method__c — linked to the Recurring_Donation__c and to the donor (Contact__c or Donor_Account__c, per the Person Account rule). The raw card number is never stored anywhere in Salesforce; only Stripe's tokenized reference is.

Payment_Method__c exists only for this purpose — see the note in Donations & payments if you're looking for a one-time gift's payment details, which live directly on the Opportunity instead.

What breaks if a card stops working#

An expiring card is only interesting because of what it is paying for. The saved card's record page answers that directly: the live recurring schedules charged on it, and what they commit to over a year. That is the number to quote when you ask a donor to update their details — "your $40 monthly gift" lands differently from "your card is expiring".

One thing on that card is worth understanding. A schedule can be charged on a saved card in two ways: through the card record it points at, or through the raw Stripe token stored on the schedule itself. The renewal batch charges the token either way, so both are genuinely billed to this card — but only the first says so anywhere on the schedule record. The card flags the second kind rather than leaving it invisible, because a schedule you can't see from here is exactly the one that breaks without warning.

A saved card belongs to the account that took it#

A tokenized card is not portable between Stripe accounts. The token your donor's card became is meaningful only to the Stripe account that created it — hand it to a different account and the charge is rejected as an unknown payment method, no matter that the card itself is perfectly good.

That matters because a campaign's Payment Account is a field an admin can change, and changing it is a legitimate thing to do (a new entity, a rebranded account, a merged program). What must not happen is a monthly donor's next charge quietly following the campaign to an account that never met their card.

So each saved card records the account that issued it, and every renewal resolves the account to charge in this order:

Order Source Used when
1 The saved card's own Payment Account Always, if the schedule has a saved card — this is the only account that can actually charge the token
2 The schedule's Payment Account The snapshot taken when the donor signed up, used when the schedule has no saved card
3 The campaign's Payment Account Last resort — an older schedule from before either field existed

The campaign is where the account is chosen for new gifts, and it is the last thing consulted for an existing one. Repointing a campaign at a different Stripe account changes where its future donations land; it does not migrate the recurring gifts already running against the old account.

Existing schedules are filled in on upgrade

Installing the version that added these fields backfills them for schedules and cards that predate it, from the campaign each one was signed up against. Nothing needs to be done by hand.

The renewal cycle#

flowchart LR
    A[RecurringDonationScheduler<br/>daily, 2 AM] --> B[RecurringRenewalBatch]
    B --> C{Due for renewal?}
    C -->|Yes| D[RecurringDonationService.processRenewal]
    D --> E[StripeGateway.chargeOffSession]
    E -->|Success| F[New Opportunity, Closed Won]
    E -->|Fails| G[Retry counter increments]
    G --> H{Retries exhausted<br/>+ grace period elapsed?}
    H -->|Yes| I[RecurringAutoCancelBatch<br/>cancels the recurring gift]
    H -->|No| B

A RecurringDonationScheduler job runs nightly and hands due renewals to RecurringRenewalBatch, which calls RecurringDonationService.processRenewal() for each — this charges the saved payment method off-session through the same IPaymentGateway abstraction used for one-time gifts, and creates a new Opportunity for the successful charge.

If the donor chose to cover processing fees when they signed up, that choice rides on the schedule rather than being baked into its amount — the schedule keeps tracking the gift the donor picked, and each renewal re-grosses it against the current fee rate. Change your processor's rate in Settings and every covered donor's next charge follows automatically. See Donations & payments.

Gifts that end on their own#

By default a recurring gift runs until someone stops it. Two optional fields on Recurring_Donation__c let it finish by itself instead — useful for a pledge the donor framed as a promise ("$50 a month for a year") rather than an open commitment:

Field Meaning When it's checked
Installment Cap End after this many successful charges After each charge — the count is only consumed by money that actually moved
End Date Never charge after this date Before each charge — the schedule stops being due, inclusive of the end date itself

A schedule that finishes its term lands on the status Completed, not Cancelled. That distinction is the point: a fulfilled pledge and a donor who walked away are very different things to a fundraiser, and lumping them together makes retention reporting lie. Installments Paid counts every successful charge including the first gift taken at signup, so a 12-payment pledge completes on its twelfth charge.

Because a skipped cycle, a failed charge, and a retry never increment the count, a donor who skips a month still gets all twelve payments they promised — the term just finishes a month later.

When a charge fails: dunning#

A failed renewal doesn't cancel the recurring gift immediately. It's retried on a schedule, with the donor notified by email at a configurable cadence, until either the charge succeeds or the org's configured retry/grace-period limits are exhausted — at which point RecurringAutoCancelBatch cancels the recurring gift and (optionally) notifies the donor.

All of this is configurable from Settings → Payments → Recurring:

Setting Default Controls
Failed Payment Retries 3 How many times a failed renewal is retried before giving up
Retry Interval (days) 3 Flat number of days between retry attempts
Grace Period (days) 7 How long a recurring gift stays active after retries are exhausted, before auto-cancel
Dunning Email Frequency 3 How often (in days) the donor is emailed about a failed payment

See Configure recurring dunning for a walkthrough of tuning these.

Donor self-service#

A donor can manage their recurring gift from the donor portal — a token-authorized page, no login required. From there they can:

  • Change the amount — the new amount takes effect on the next charge; the billing day never moves, so raising a gift doesn't quietly pull the next payment forward. If the schedule splits across several funds by percentage, those splits are re-derived at the new amount.
  • Change the fund — only funds the campaign actually offers are accepted. If the campaign wouldn't allow the requested fund, the change is refused rather than silently redirected to the campaign's default; a donor who asked for the scholarship fund should not end up funding something else without being told.
  • Skip the next payment — moves the next payment date on by one cycle without consuming an installment of a pledge term. Unavailable mid-dunning, where the schedule has a failed charge to resolve first.
  • Switch or add a saved card, or cancel the gift entirely.

Every one of these writes a Recurring_Donation_History__c row. The schedule's own fields keep no history of their prior values, so that trail is the only record of what a donor changed and when — worth pointing a fundraiser at when they ask why a monthly gift is a different size than they remember.

Each action re-checks that the token actually owns the schedule it names, so a guessed record Id gets a refusal rather than someone else's gift.

Doing it for them: Manage Recurring Gift#

Donors phone. Manage Recurring Gift, on the recurring gift's record page, gives a fundraiser the same set of changes the donor has in the portal, without asking them to go find their email.

The panel opens on the gift's current state — status, amount, frequency, next payment date, any failed charges — and offers only the actions that are actually available on a schedule in that state. A paused gift offers Resume, not Pause. A healthy gift offers no Retry Now, because there is nothing that failed to retry; on a gift mid-dunning it charges immediately instead of waiting for the next scheduled attempt.

Action What it does
Pause / Resume Stops charging without ending the gift; resuming picks the existing schedule back up
Skip next payment Moves the next payment on by one cycle, without consuming a pledge installment
Retry now Charges a failed or mid-dunning gift immediately rather than waiting for the next retry
Change amount Takes effect on the next charge; percentage fund splits are re-derived at the new amount
Change fund Offers exactly the funds the campaign offers the donor — the same resolved list, not a wider staff-only one
Reinstate Restarts a cancelled or failed gift: billing resumes today and the dunning counters are cleared
Switch card Moves the gift onto another card the donor has already saved

Cancelling is deliberately not here — it's the separate Stop Recurring Donation action, because it's terminal and needs a reason, and an irreversible button sitting one click from the reversible ones is a mistake waiting to happen.

Everything a fundraiser does through this panel writes the same history row a donor's own change would, so the audit trail doesn't distinguish based on who was more convenient to ask.

There is no way to key in a donor's card

The card picker lists only cards already on file, and there is no "add a card" path for staff by design. Tokenizing a card needs the donor's own browser and their own mandate — a fundraiser typing card details into Salesforce would breach both PCI and the donor's consent. When a donor needs to put a new card on file, send them their portal link (see Donors & accounts) and let them add it themselves. A donor with only one card on file simply sees no switch option, which is the honest answer: there is nothing to switch to.

Who can use it#

The panel is gated by the Manage Recurring Gifts custom permission, granted by Fundraising Admin. Without it the panel still opens — it just renders read-only, so anyone who can see the gift can see its state and answer a donor's question, and only someone trusted with the permission can change it. Add the permission to another permission set if you want a wider group of fundraisers making these changes.

Every action re-checks the permission and the schedule's own rules on the server. A panel left open on a stale screen can't be used to push through a change the schedule no longer allows.

  • Recurring Donation fields


    Complete field-level reference.

    Data model →

  • Payment Method fields


    Where saved cards are referenced, and by what.

    Data model →

  • Configure dunning


    A step-by-step guide to the retry/grace-period settings.

    Guide →