Skip to main content

Person Mapping

This document details the real mapping between WeGive donor records and Planning Center Person objects, including contact information. The mapping is narrower than a full CRM sync — no demographic fields, no household/family relationships, and matching is by correlation ID only.

Core Person Mapping

Primary Fields

There is no middle_name pulled back (it’s push-only), and no suffix, birthdate, anniversary, gender, created_at, or updated_at field mapping. Planning Center’s own status attribute is pulled and mapped to WeGive’s enabled flag (status !== 'inactive') — that’s the only other person-level field synced.

Contact Information Mapping

Email Address Management

Fields synced: up to 3 emails, positional — email_1, email_2, email_3. Push behavior:
  • Only runs when a donor is first created, or on every update via syncDonorContactInfo()
  • Fetches the person’s current Planning Center emails first; an email already present (exact string match) is skipped, not re-sent
  • New emails are posted with primary: true only for email_1; email_2/email_3 are primary: false
  • Location is always the literal string "Email" — there’s no “home”/“work”/“other” categorization
  • A 409 or 422 response (Planning Center considers the email a duplicate) is treated as success and skipped silently — not an error
Pull behavior: imports up to 3 emails from Planning Center by array position (not by any “primary” designation) into email_1/email_2/email_3.

Phone Number Management

Fields synced: up to 2 phone numbers, positional — mobile_phone, office_phone. There is no home_phone/work_phone distinction anywhere in the code. Push behavior:
  • Same current-value-check-then-post pattern as email
  • primary: true only for mobile_phone
  • Location is always the literal string "Phone Number" — no “mobile”/“home”/“work” categorization
  • Same 409/422-is-a-silent-skip behavior as email
Pull behavior: imports up to 2 phone numbers by position into mobile_phone/office_phone.

Address Management

Fields synced: one mailing address only — address_1 (from street_line_1), city, state, zip. There’s no street_2, no country field mapped on push, and no address type/categorization beyond the fixed location string "Mailing Address". Push behavior:
  • Only syncs if the address has non-empty address_1, city, state, and zip — an incomplete address is skipped entirely, not partially synced
  • Finds the existing address marked primary in Planning Center and PATCHes it if found, otherwise POSTs a new one
  • Always sent with primary: true
Pull behavior: imports the first address in the response array (addresses.0) into WeGive’s mailing address, with a country default of "US" if Planning Center doesn’t provide one.

Data Transformation

Person Creation Processing (Push)

  1. Acquire a per-donor Redis lock (600s) to prevent duplicate creation under concurrent pushes
  2. Create the Person with first_name/last_name only ("FNU"/"LNU" placeholders if blank)
  3. Store the returned id as planning_center_id
  4. Call syncDonorContactInfo() to add emails/phones/address

Person Update Processing (Push)

  1. PATCH the Person’s first_name/last_name by planning_center_id
  2. If the PATCH returns 404 (person deleted in Planning Center), clear the stale planning_center_id and create a new Person instead — this is a full re-create, not a narrower “confirm and skip” check
  3. Call syncDonorContactInfo() again regardless

Person Import Processing (Pull)

  1. Match by planning_center_id; create a new WeGive Donor if no match
  2. Map first_name/last_name/middle_name/status and the positional email/phone arrays
  3. Create or update the mailing address from addresses.0

Matching and Deduplication

Person Matching Strategy

The only matching mechanism, on push and pull alike, is planning_center_id. There is no email-based, name-based, or partial-name-plus-phone matching anywhere in this integration. A donor without a stored planning_center_id always results in a new Planning Center Person being created — even if a person with the same name or email already exists there.
This matters for duplicate-contact tickets: the fix isn’t “improve matching logic” (there is none to improve) — it’s ensuring the donor gets linked to the correct existing Planning Center person (via planning_center_id) before its first push.

Conflict Resolution

  • Push: WeGive’s first_name/last_name always overwrite Planning Center’s on update; contact info is additive (existing values aren’t removed, missing values are added if not already present)
  • Pull: Planning Center’s data always overwrites WeGive’s mapped fields

Validation Rules

  • Planning Center requires at least one of first_name/last_name — WeGive satisfies this unconditionally via the "FNU"/"LNU" placeholders, so a blank-name donor never fails Person creation
  • Email/phone addition is skipped gracefully (not treated as an error) if Planning Center reports it as a duplicate
  • An address with any of address_1/city/state/zip empty is skipped entirely on push — there’s no partial-address sync

Known Gaps (not implemented)

  • Demographic fields: birthdate, anniversary, gender
  • Household/family relationships of any kind
  • Suffix, nickname, or middle-name-on-pull
  • More than 3 emails or 2 phone numbers
  • More than one address, or any address type beyond “mailing”
  • Communication/opt-out preference sync
  • Any performance/caching optimization beyond the existing-value check before each push