Skip to main content

RecurringGift Mapping

WeGive Scheduled Donations map to Virtuous RecurringGift records.

Record Model

Frequency Mapping

Push (WeGive → Virtuous)

Before pushing, the integration first pushes any related records that lack a virtuous_id — the donor (Contact), the campaign (Segment), and the fund(s) (Projects).

Creating a recurring gift

The returned id is stored as the scheduled donation’s virtuous_id.
nextExpectedPaymentDate isn’t simply start_date — if the plan’s start_date is today, in the past, or within the next day, it’s bumped forward to 2 days from now instead. This avoids telling Virtuous to expect a payment that’s already due or overdue at creation time.

Amount and fees

The pushed amount is the charge_amount (base amount plus fees), not the base amount. The designations are built so their sum equals charge_amount:
  • When fee-splitting is on (or no fee fund is configured), the fee is distributed proportionally across the allocation designations, with rounding absorbed by the last row.
  • When a dedicated fee fund is configured, the fee is added as (or merged into) a separate designation for that fund.
This mirrors WeGive’s own recurring fee allocation so per-installment gifts and the recurring plan stay aligned in Virtuous.

Updating a recurring gift

When the scheduled donation already has a virtuous_id, the integration GETs the RecurringGift and overwrites the WeGive-owned fields, then PUTs it back:
  • nextExpectedPaymentDate, amount, isPrivate, segmentId
  • designations — replaced entirely (WeGive is the source of truth for allocations)

Pull (Virtuous → WeGive)

RecurringGifts are pulled via POST RecurringGift/Query (1000 per page, filtered by the configured pull_by date) and imported as Scheduled Donations, matched on virtuous_id:
amount and fee_amount are set only on first import. On existing records, WeGive is the source of truth — Virtuous stores the fee-inflated charge_amount, so pulling it would create a feedback loop of growing fees.
Source resolution: a company donor (by virtuous_contact_id, no individual) is tried first; otherwise the Contact’s primary individual. Fund comes from the first designation’s projectId; campaign from segmentId. When fund allocations are enabled, allocations are synced to WeGive from the designations on first import only (amountDesignated * 100 → allocation amount). This reflects the integration as implemented in app/Integrations/Virtuous.php.