Skip to main content

Donation Mapping

This document details the real mapping between WeGive Transaction objects and Planning Center Donation records. Transactions sync exclusively through the once-daily batch job — there is no real-time transaction push (pushTransaction() is a no-op stub) — and each donation gets exactly one fund designation; split/multi-fund gifts aren’t supported.

Core Donation Mapping

Primary Fields

There’s no currency, memo, or generic reference field mapping — this integration doesn’t track or send those. Currency is implicitly USD; there’s no multi-currency support.

Batch Processing Integration

Daily Batch Organization

Real batch mechanics (confirmed against GeneratePlanningCenterBatchesJob):
  • One scheduled job, once per day at 8:01 AM, per enabled integration
  • Selects WeGive transactions by succeeded_at falling within the previous calendar day (in the organization’s own timezone), with status = success, amount > 0, integration_enabled = 1, and no planning_center_id yet
  • Every run also does a second “missing” catch-up pass with no date bound, covering any transaction that still lacks a planning_center_id for any reason
  • Batch name format: WeGive Import MM/DD/YYYY
  • A day with zero eligible transactions logs the batch as ignored — no Planning Center batch is created for it at all
Batch commitment:
  • The batch is looked up by matching description in Planning Center, or created if not found
  • After all donations in the run are processed, the batch is committed automatically if its status is in_progress — there’s no manual/uncommitted state exposed anywhere

Batch Rollback (Error Handling)

If anything in createTransactionsBatch() throws (outside the per-transaction catch described below — e.g. the initial batch/payment-source lookup fails), the entire run is rolled back:
  • Any planning_center_ids set on transactions during that run are cleared back to null
  • The partial batch itself is deleted in Planning Center
This is a genuine all-or-nothing rollback at the run level, but doesn’t apply to individual transaction failures within a successful run (see below).

Fund Designation Mapping

Single Fund Designation (the only kind supported)

Each donation includes exactly one Designation object, resolved in this fixed priority order:
  1. The transaction’s own linked fund, if it has a planning_center_id
  2. The integration’s configured default_fund_id
  3. The destination organization’s oldest fund that has a planning_center_id
  4. If none resolve: the transaction throws Unknown Fund Source and is skipped (not the whole batch)
There is no split/multi-fund designation support in this integration — a WeGive transaction with multiple fund allocations still produces exactly one Planning Center Designation, tied to whichever fund resolves from the priority order above. The full transaction amount goes to that one fund.

Payment Source Management

WeGive Payment Source

  • A single Planning Center payment source named “WeGive” is found by name or created automatically, once per batch run, and reused for every donation in that run
  • The original payment method is preserved only via the donation’s own payment_method attribute (card or ach) — there’s no separate metadata field storing card type, last-4, bank name, or check number
There is no per-payment-method breakdown (“Bank/Check/Cash” mapping) — every WeGive source type other than card resolves to ach in the payment_method field sent to Planning Center. Check and cash transactions aren’t distinguished.

Data Transformation

Donation Creation Processing (Push, via daily batch)

  1. Find-or-create the day’s batch and the “WeGive” payment source (once per run)
  2. For each eligible transaction: push the donor (if not already linked) and the fund (if not already linked)
  3. Resolve the fund via the priority order above; skip (catch + report to Sentry) if none resolves
  4. POST the donation with amount_cents, payment_method, payment_source_id, person_id, received_at, plus one nested Designation
  5. Store the returned donation ID as planning_center_id on the transaction
  6. Commit the batch once all transactions are processed

Pull Operation (Planning Center → WeGive)

  1. Page through giving/v2/donations (100 per page), including designations
  2. For each donation, resolve its fund from the included designations/fund relationship (first designation only)
  3. Match the donor by planning_center_id; throw 'No owner found' if none exists (this transaction is skipped, not created with a null owner)
  4. Match the fund by planning_center_id if present
  5. Match a scheduled donation by the pulled donation’s recurring_donation relationship, if present (read-only correlation — see below)
  6. Guard against CRM-owned-field overwrite: if the local transaction already has a correlation_id (it was actually processed by WeGive) or is still future-dated-pending, its amount and fund allocations are preserved as-is — a Planning Center-side edit does not overwrite the real payment data. Otherwise, amount and (if fund allocations are enabled for the org) a single fund allocation are rebuilt from the pulled data.

Recurring Donation Integration

There is no independent recurring-gift sync in this integration. A pulled donation’s relationship to a Planning Center recurring donation is read and used only to link the resulting WeGive Transaction to an existing ScheduledDonation record by planning_center_id — WeGive never creates, updates, or pushes a recurring-gift schedule to Planning Center. If no matching ScheduledDonation exists locally, the link is simply not made; nothing fails because of it.

Validation and Error Handling

Donation Validation

  • Non-positive transaction amounts are silently skipped on push (Planning Center’s API requires positive amounts) — not an error, not logged
  • A transaction whose fund can’t resolve throws Unknown Fund Source, caught per-transaction and reported to Sentry — the transaction stays unlinked (no planning_center_id) and is re-attempted on the next run
  • A pulled donation with no matching donor throws 'No owner found' — that donation import fails; there’s no “create the donor from the donation” fallback

Batch Error Handling

  • A single transaction’s push failure (thrown inside the per-transaction try/catch) is caught, reported to Sentry, and the loop continues — it does not fail the whole batch
  • A failure in batch-level setup (the batch lookup, the payment-source lookup/creation) is not caught per-transaction — it propagates up and triggers the full run-level rollback described above

Performance Considerations

  • All transaction sync happens through the once-daily batch job — there’s no way to force an immediate/real-time transaction push
  • API calls are throttled to Planning Center’s real limit (70 requests / 20 seconds) via a hardcoded internal counter
  • GET requests (fund/payment-source lookups, donation/fund pulls) are retried up to 3 times on transient failures; the mutating POST/PATCH/DELETE calls that create the actual records are not automatically retried on failure

Known Gaps (not implemented)

  • Real-time/immediate transaction push
  • Split/multi-fund designations per donation
  • Currency field beyond implicit USD
  • Memo/reference field sync
  • Distinct payment-method categories beyond card/ach
  • Independent recurring-gift creation or schedule sync