diff --git a/Status.md b/Status.md
index cdcf8d86..4de1072d 100644
--- a/Status.md
+++ b/Status.md
@@ -6,7 +6,76 @@
---
-## 🎯 CURRENT SESSION - GEMINI TRANSITION & VALIDATION (Dec 16, 2025)
+## 🎯 CURRENT SESSION - ZOHO CRM INTEGRATION SETUP (Dec 16, 2025)
+
+### Zoho CRM Integration - Staging Environment
+
+**Objective:** Configure and test Zoho CRM sync implementation for staging environment.
+
+**Status:** ✅ OAuth Working, Sync Methods Implemented, Dry-Run Tested
+
+**Completed:**
+
+1. ✅ **OAuth Authentication Verified**
+ - Refresh token exists and is valid
+ - API connection successful (53 modules accessible)
+ - Read operations working (Contacts, Campaigns, Users)
+
+2. ✅ **Read-Only API Tests Passed**
+ - Organization Info: Manifold Cloud Services (America/Detroit)
+ - Contacts: 5+ records readable (Tanner Moore, Pete Knochelmann, etc.)
+ - Campaigns: 5+ records readable (Nov 28, Oct 23, etc.)
+ - CRM Users: Ben Reed (CEO), JR Lawhorne (Manager), etc.
+
+3. ✅ **Sync Class Bug Fixes**
+ - Fixed user roles: `trainer`/`trainee` → `hvac_trainer`/`hvac_master_trainer`
+ - Fixed event filter: Removed restrictive `_hvac_event_type` meta query
+ - Fixed event display: Changed `eventDisplay` from `list` to `custom` to include past events
+ - Fixed WooCommerce dependency: Added graceful error handling
+
+4. ✅ **Event Tickets Integration (NEW)**
+ - Replaced WooCommerce sync with Event Tickets (Tickets Commerce) support
+ - Added `sync_attendees()` method → Zoho Contacts + Campaign Members
+ - Added `sync_rsvps()` method → Zoho Leads + Campaign Members
+ - Updated meta keys for Tickets Commerce (`_tec_tickets_commerce_*`)
+ - Updated meta keys for RSVPs (`_tribe_rsvp_*`)
+
+5. ✅ **Admin Interface Updated**
+ - Added "Sync Attendees" button (Contacts + Campaign Members)
+ - Added "Sync RSVPs" button (Leads + Campaign Members)
+ - Renamed "Sync Purchases" to "Sync Orders" (Tickets Commerce)
+
+**Dry-Run Results (Staging - No Data Sent to Zoho):**
+| Sync Type | Records Found | Status |
+|-----------|---------------|--------|
+| Events → Campaigns | 20 | ✅ Ready |
+| Trainers → Contacts | 53 | ✅ Ready |
+| Attendees → Contacts + Campaign Members | 79 | ✅ Ready |
+| RSVPs → Leads + Campaign Members | 4 | ✅ Ready |
+| Orders → Invoices | 52 | ✅ Ready |
+
+**Zoho CRM Mapping Strategy:**
+- **Events** → **Campaigns** (direct mapping)
+- **Trainers** (hvac_trainer, hvac_master_trainer) → **Contacts** (with Contact_Type field)
+- **Ticket Attendees** → **Contacts** + **Campaign Members** (links Contact ↔ Campaign)
+- **RSVPs** → **Leads** + **Campaign Members** (links Lead ↔ Campaign)
+- **Ticket Orders** → **Invoices** (financial records)
+
+**Staging Protection Active:**
+- All write operations (POST/PUT/DELETE) are blocked on staging
+- Only production (`upskillhvac.com`) can write to Zoho CRM
+- Dry-run shows what would sync without actually sending data
+
+**Admin Page Location:**
+- `/wp-admin/admin.php?page=hvac-zoho-sync`
+
+**Files Modified:**
+- `includes/zoho/class-zoho-sync.php` - Complete rewrite for Event Tickets
+- `includes/admin/class-zoho-admin.php` - Added new sync buttons
+
+---
+
+## 📅 PREVIOUS SESSION - GEMINI TRANSITION & VALIDATION (Dec 16, 2025)
### Gemini Development Environment Setup
diff --git a/includes/admin/class-zoho-admin.php b/includes/admin/class-zoho-admin.php
index b5eb16f6..7500c01f 100644
--- a/includes/admin/class-zoho-admin.php
+++ b/includes/admin/class-zoho-admin.php
@@ -284,25 +284,39 @@ class HVAC_Zoho_Admin {
Data Sync
-
+
Events → Campaigns
Sync events from The Events Calendar to Zoho CRM Campaigns
-
+
-
Users → Contacts
-
Sync trainers and attendees to Zoho CRM Contacts
-
+
Trainers → Contacts
+
Sync trainers (hvac_trainer, hvac_master_trainer) to Zoho CRM Contacts
+
-
+
-
Purchases → Invoices
-
Sync ticket purchases to Zoho CRM Invoices
-
+
Ticket Attendees → Contacts + Campaign Members
+
Sync Event Tickets attendees to Zoho CRM Contacts and link them to Campaigns
+
+
+
+
+
+
RSVPs → Leads + Campaign Members
+
Sync RSVP responses to Zoho CRM Leads and link them to Campaigns
+
+
+
+
+
+
Ticket Orders → Invoices
+
Sync Event Tickets orders (Tickets Commerce) to Zoho CRM Invoices
+
@@ -906,6 +920,12 @@ class HVAC_Zoho_Admin {
case 'users':
$result = $sync->sync_users();
break;
+ case 'attendees':
+ $result = $sync->sync_attendees();
+ break;
+ case 'rsvps':
+ $result = $sync->sync_rsvps();
+ break;
case 'purchases':
$result = $sync->sync_purchases();
break;
diff --git a/includes/zoho/class-zoho-sync.php b/includes/zoho/class-zoho-sync.php
index 8523d543..9766d338 100644
--- a/includes/zoho/class-zoho-sync.php
+++ b/includes/zoho/class-zoho-sync.php
@@ -65,17 +65,11 @@ class HVAC_Zoho_Sync {
'staging_mode' => $this->is_staging
);
- // Get all published events
+ // Get all published events (past and future - 'custom' bypasses date filtering)
$events = tribe_get_events(array(
'posts_per_page' => -1,
- 'eventDisplay' => 'list',
- 'meta_query' => array(
- array(
- 'key' => '_hvac_event_type',
- 'value' => 'trainer',
- 'compare' => '='
- )
- )
+ 'eventDisplay' => 'custom',
+ 'start_date' => '2020-01-01', // Include all historical events
));
$results['total'] = count($events);
@@ -156,9 +150,9 @@ class HVAC_Zoho_Sync {
'staging_mode' => $this->is_staging
);
- // Get trainers and attendees
+ // Get trainers (hvac_trainer and hvac_master_trainer roles)
$users = get_users(array(
- 'role__in' => array('trainer', 'trainee'),
+ 'role__in' => array('hvac_trainer', 'hvac_master_trainer'),
'meta_query' => array(
'relation' => 'OR',
array(
@@ -243,7 +237,7 @@ class HVAC_Zoho_Sync {
}
/**
- * Sync ticket purchases to Zoho Invoices
+ * Sync Event Tickets orders to Zoho Invoices
*
* @return array Sync results
*/
@@ -255,81 +249,369 @@ class HVAC_Zoho_Sync {
'errors' => array(),
'staging_mode' => $this->is_staging
);
-
- // Get all completed orders
- $orders = wc_get_orders(array(
- 'status' => 'completed',
- 'limit' => -1,
- 'meta_key' => '_tribe_tickets_event_id',
- 'meta_compare' => 'EXISTS'
+
+ // Get Tickets Commerce orders (tec_tc_order post type)
+ $orders = get_posts(array(
+ 'post_type' => 'tec_tc_order',
+ 'posts_per_page' => -1,
+ 'post_status' => 'tec-tc-completed',
));
-
+
$results['total'] = count($orders);
-
+
+ if ($results['total'] === 0) {
+ $results['message'] = 'No completed ticket orders found.';
+ return $results;
+ }
+
// If staging mode, simulate the sync
if ($this->is_staging) {
$results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
$results['synced'] = $results['total'];
$results['test_data'] = array();
-
+
foreach ($orders as $order) {
- $invoice_data = $this->prepare_invoice_data($order);
+ $invoice_data = $this->prepare_tc_invoice_data($order);
$results['test_data'][] = array(
- 'order_id' => $order->get_id(),
- 'order_number' => $order->get_order_number(),
- 'order_total' => $order->get_total(),
+ 'order_id' => $order->ID,
+ 'purchaser_email' => get_post_meta($order->ID, '_tec_tc_order_purchaser_email', true),
+ 'gateway' => get_post_meta($order->ID, '_tec_tc_order_gateway', true),
+ 'date' => $order->post_date,
'zoho_data' => $invoice_data
);
}
-
+
return $results;
}
-
+
// Production sync
if (!$this->is_sync_allowed()) {
$results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
return $results;
}
-
+
foreach ($orders as $order) {
try {
- $invoice_data = $this->prepare_invoice_data($order);
-
- // Check if invoice already exists in Zoho
- $order_number = $order->get_order_number();
- $search_response = $this->auth->make_api_request('GET', '/crm/v2/Invoices/search', array(
- 'criteria' => "(Invoice_Number:equals:{$order_number})"
- ));
-
+ $invoice_data = $this->prepare_tc_invoice_data($order);
+
+ // Check if invoice already exists in Zoho (by WordPress Order ID)
+ $search_response = $this->auth->make_api_request(
+ '/Invoices/search?criteria=(WordPress_Order_ID:equals:' . $order->ID . ')',
+ 'GET'
+ );
+
if (!empty($search_response['data'])) {
// Update existing invoice
$invoice_id = $search_response['data'][0]['id'];
- $update_response = $this->auth->make_api_request('PUT', "/crm/v2/Invoices/{$invoice_id}", array(
+ $this->auth->make_api_request("/Invoices/{$invoice_id}", 'PUT', array(
'data' => array($invoice_data)
));
} else {
// Create new invoice
- $create_response = $this->auth->make_api_request('POST', '/crm/v2/Invoices', array(
+ $create_response = $this->auth->make_api_request('/Invoices', 'POST', array(
'data' => array($invoice_data)
));
+
+ if (!empty($create_response['data'][0]['details']['id'])) {
+ $invoice_id = $create_response['data'][0]['details']['id'];
+ }
}
-
+
$results['synced']++;
-
+
// Update order meta with Zoho ID
if (isset($invoice_id)) {
- $order->update_meta_data('_zoho_invoice_id', $invoice_id);
- $order->save();
+ update_post_meta($order->ID, '_zoho_invoice_id', $invoice_id);
}
-
+
} catch (Exception $e) {
$results['failed']++;
- $results['errors'][] = sprintf('Order %s: %s', $order->get_id(), $e->getMessage());
+ $results['errors'][] = sprintf('Order %s: %s', $order->ID, $e->getMessage());
}
}
-
+
return $results;
}
+
+ /**
+ * Sync ticket attendees to Zoho Contacts + Campaign Members
+ *
+ * @return array Sync results
+ */
+ public function sync_attendees() {
+ $results = array(
+ 'total' => 0,
+ 'synced' => 0,
+ 'failed' => 0,
+ 'errors' => array(),
+ 'staging_mode' => $this->is_staging,
+ 'contacts_created' => 0,
+ 'campaign_members_created' => 0
+ );
+
+ // Get all ticket attendees (Tickets Commerce)
+ $attendees = get_posts(array(
+ 'post_type' => 'tec_tc_attendee',
+ 'posts_per_page' => -1,
+ 'post_status' => 'any',
+ ));
+
+ // Also get PayPal attendees if any
+ $tpp_attendees = get_posts(array(
+ 'post_type' => 'tribe_tpp_attendees',
+ 'posts_per_page' => -1,
+ 'post_status' => 'any',
+ ));
+
+ $all_attendees = array_merge($attendees, $tpp_attendees);
+ $results['total'] = count($all_attendees);
+
+ if ($results['total'] === 0) {
+ $results['message'] = 'No ticket attendees found.';
+ return $results;
+ }
+
+ // If staging mode, simulate the sync
+ if ($this->is_staging) {
+ $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
+ $results['synced'] = $results['total'];
+ $results['test_data'] = array();
+
+ foreach ($all_attendees as $attendee) {
+ $attendee_data = $this->prepare_attendee_data($attendee);
+ $results['test_data'][] = $attendee_data;
+ }
+
+ return $results;
+ }
+
+ // Production sync
+ if (!$this->is_sync_allowed()) {
+ $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
+ return $results;
+ }
+
+ foreach ($all_attendees as $attendee) {
+ try {
+ $attendee_data = $this->prepare_attendee_data($attendee);
+
+ if (empty($attendee_data['email'])) {
+ $results['failed']++;
+ $results['errors'][] = sprintf('Attendee %s: No email address', $attendee->ID);
+ continue;
+ }
+
+ // Step 1: Create/Update Contact
+ $contact_id = $this->ensure_contact_exists($attendee_data);
+ if ($contact_id) {
+ $results['contacts_created']++;
+ }
+
+ // Step 2: Create Campaign Member (link Contact to Campaign)
+ if ($contact_id && !empty($attendee_data['event_id'])) {
+ $campaign_id = get_post_meta($attendee_data['event_id'], '_zoho_campaign_id', true);
+ if ($campaign_id) {
+ $this->create_campaign_member($contact_id, $campaign_id, 'Attended');
+ $results['campaign_members_created']++;
+ }
+ }
+
+ $results['synced']++;
+
+ // Update attendee meta with Zoho Contact ID
+ update_post_meta($attendee->ID, '_zoho_contact_id', $contact_id);
+
+ } catch (Exception $e) {
+ $results['failed']++;
+ $results['errors'][] = sprintf('Attendee %s: %s', $attendee->ID, $e->getMessage());
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Sync RSVPs to Zoho Leads + Campaign Members
+ *
+ * @return array Sync results
+ */
+ public function sync_rsvps() {
+ $results = array(
+ 'total' => 0,
+ 'synced' => 0,
+ 'failed' => 0,
+ 'errors' => array(),
+ 'staging_mode' => $this->is_staging,
+ 'leads_created' => 0,
+ 'campaign_members_created' => 0
+ );
+
+ // Get RSVP attendees
+ $rsvps = get_posts(array(
+ 'post_type' => 'tribe_rsvp_attendees',
+ 'posts_per_page' => -1,
+ 'post_status' => 'any',
+ ));
+
+ $results['total'] = count($rsvps);
+
+ if ($results['total'] === 0) {
+ $results['message'] = 'No RSVP attendees found.';
+ return $results;
+ }
+
+ // If staging mode, simulate the sync
+ if ($this->is_staging) {
+ $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
+ $results['synced'] = $results['total'];
+ $results['test_data'] = array();
+
+ foreach ($rsvps as $rsvp) {
+ $rsvp_data = $this->prepare_rsvp_data($rsvp);
+ $results['test_data'][] = $rsvp_data;
+ }
+
+ return $results;
+ }
+
+ // Production sync
+ if (!$this->is_sync_allowed()) {
+ $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
+ return $results;
+ }
+
+ foreach ($rsvps as $rsvp) {
+ try {
+ $rsvp_data = $this->prepare_rsvp_data($rsvp);
+
+ if (empty($rsvp_data['email'])) {
+ $results['failed']++;
+ $results['errors'][] = sprintf('RSVP %s: No email address', $rsvp->ID);
+ continue;
+ }
+
+ // Step 1: Create/Update Lead
+ $lead_id = $this->ensure_lead_exists($rsvp_data);
+ if ($lead_id) {
+ $results['leads_created']++;
+ }
+
+ // Step 2: Create Campaign Member (link Lead to Campaign)
+ if ($lead_id && !empty($rsvp_data['event_id'])) {
+ $campaign_id = get_post_meta($rsvp_data['event_id'], '_zoho_campaign_id', true);
+ if ($campaign_id) {
+ $this->create_campaign_member($lead_id, $campaign_id, 'Responded', 'Leads');
+ $results['campaign_members_created']++;
+ }
+ }
+
+ $results['synced']++;
+
+ // Update RSVP meta with Zoho Lead ID
+ update_post_meta($rsvp->ID, '_zoho_lead_id', $lead_id);
+
+ } catch (Exception $e) {
+ $results['failed']++;
+ $results['errors'][] = sprintf('RSVP %s: %s', $rsvp->ID, $e->getMessage());
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Ensure a Contact exists in Zoho (create or get existing)
+ *
+ * @param array $data Attendee data
+ * @return string|null Zoho Contact ID
+ */
+ private function ensure_contact_exists($data) {
+ // Search for existing contact by email
+ $search_response = $this->auth->make_api_request(
+ '/Contacts/search?criteria=(Email:equals:' . urlencode($data['email']) . ')',
+ 'GET'
+ );
+
+ if (!empty($search_response['data'])) {
+ return $search_response['data'][0]['id'];
+ }
+
+ // Create new contact
+ $contact_data = array(
+ 'First_Name' => $data['first_name'] ?: 'Unknown',
+ 'Last_Name' => $data['last_name'] ?: 'Attendee',
+ 'Email' => $data['email'],
+ 'Lead_Source' => 'Event Tickets',
+ 'Contact_Type' => 'Attendee',
+ 'WordPress_Attendee_ID' => $data['attendee_id'],
+ );
+
+ $create_response = $this->auth->make_api_request('/Contacts', 'POST', array(
+ 'data' => array($contact_data)
+ ));
+
+ if (!empty($create_response['data'][0]['details']['id'])) {
+ return $create_response['data'][0]['details']['id'];
+ }
+
+ return null;
+ }
+
+ /**
+ * Ensure a Lead exists in Zoho (create or get existing)
+ *
+ * @param array $data RSVP data
+ * @return string|null Zoho Lead ID
+ */
+ private function ensure_lead_exists($data) {
+ // Search for existing lead by email
+ $search_response = $this->auth->make_api_request(
+ '/Leads/search?criteria=(Email:equals:' . urlencode($data['email']) . ')',
+ 'GET'
+ );
+
+ if (!empty($search_response['data'])) {
+ return $search_response['data'][0]['id'];
+ }
+
+ // Create new lead
+ $lead_data = array(
+ 'First_Name' => $data['first_name'] ?: 'Unknown',
+ 'Last_Name' => $data['last_name'] ?: 'RSVP',
+ 'Email' => $data['email'],
+ 'Lead_Source' => 'Event RSVP',
+ 'Lead_Status' => 'Contacted',
+ 'WordPress_RSVP_ID' => $data['rsvp_id'],
+ );
+
+ $create_response = $this->auth->make_api_request('/Leads', 'POST', array(
+ 'data' => array($lead_data)
+ ));
+
+ if (!empty($create_response['data'][0]['details']['id'])) {
+ return $create_response['data'][0]['details']['id'];
+ }
+
+ return null;
+ }
+
+ /**
+ * Create a Campaign Member (link Contact/Lead to Campaign)
+ *
+ * @param string $record_id Contact or Lead ID
+ * @param string $campaign_id Campaign ID
+ * @param string $status Member status (Invited, Responded, Attended, etc.)
+ * @param string $type 'Contacts' or 'Leads'
+ */
+ private function create_campaign_member($record_id, $campaign_id, $status = 'Attended', $type = 'Contacts') {
+ $endpoint = "/Campaigns/{$campaign_id}/{$type}/{$record_id}";
+
+ $this->auth->make_api_request($endpoint, 'PUT', array(
+ 'data' => array(
+ array('Member_Status' => $status)
+ )
+ ));
+ }
/**
* Prepare campaign data for Zoho
@@ -365,8 +647,15 @@ class HVAC_Zoho_Sync {
* @return array Contact data
*/
private function prepare_contact_data($user) {
- $role = in_array('trainer', $user->roles) ? 'Trainer' : 'Trainee';
-
+ // Map WordPress roles to Zoho Contact_Type
+ if (in_array('hvac_master_trainer', $user->roles)) {
+ $role = 'Master Trainer';
+ } elseif (in_array('hvac_trainer', $user->roles)) {
+ $role = 'Trainer';
+ } else {
+ $role = 'Trainee';
+ }
+
return array(
'First_Name' => get_user_meta($user->ID, 'first_name', true),
'Last_Name' => get_user_meta($user->ID, 'last_name', true),
@@ -384,45 +673,163 @@ class HVAC_Zoho_Sync {
}
/**
- * Prepare invoice data for Zoho
+ * Prepare invoice data for Tickets Commerce order
*
- * @param WC_Order $order Order object
+ * @param WP_Post $order TC Order post object
* @return array Invoice data
*/
- private function prepare_invoice_data($order) {
- $event_id = $order->get_meta('_tribe_tickets_event_id');
- $event_title = get_the_title($event_id);
- $customer = $order->get_user();
-
- // Get contact ID from Zoho
- $contact_id = null;
- if ($customer) {
- $contact_id = get_user_meta($customer->ID, '_zoho_contact_id', true);
- }
-
+ private function prepare_tc_invoice_data($order) {
+ $purchaser_email = get_post_meta($order->ID, '_tec_tc_order_purchaser_email', true);
+ $purchaser_name = get_post_meta($order->ID, '_tec_tc_order_purchaser_name', true);
+ $gateway = get_post_meta($order->ID, '_tec_tc_order_gateway', true);
+ $gateway_order_id = get_post_meta($order->ID, '_tec_tc_order_gateway_order_id', true);
+ $order_items = get_post_meta($order->ID, '_tec_tc_order_items', true);
+
+ // Parse order items to get event info and line items
$items = array();
- foreach ($order->get_items() as $item) {
- $items[] = array(
- 'Product_Name' => $item->get_name(),
- 'Quantity' => $item->get_quantity(),
- 'Rate' => $item->get_subtotal() / $item->get_quantity(),
- 'Total' => $item->get_total()
- );
+ $event_titles = array();
+ $total = 0;
+
+ if (is_array($order_items)) {
+ foreach ($order_items as $item) {
+ $event_id = isset($item['event_id']) ? $item['event_id'] : 0;
+ $ticket_id = isset($item['ticket_id']) ? $item['ticket_id'] : 0;
+ $quantity = isset($item['quantity']) ? intval($item['quantity']) : 1;
+ $price = isset($item['price']) ? floatval($item['price']) : 0;
+
+ if ($event_id) {
+ $event_titles[] = get_the_title($event_id);
+ }
+
+ $ticket_name = $ticket_id ? get_the_title($ticket_id) : 'Event Ticket';
+
+ $items[] = array(
+ 'product' => array('name' => $ticket_name),
+ 'quantity' => $quantity,
+ 'list_price' => $price,
+ 'total' => $price * $quantity
+ );
+
+ $total += $price * $quantity;
+ }
}
-
+
+ $event_summary = !empty($event_titles) ? implode(', ', array_unique($event_titles)) : 'Event Tickets';
+
return array(
- 'Invoice_Number' => $order->get_order_number(),
- 'Invoice_Date' => $order->get_date_created()->format('Y-m-d'),
+ 'Subject' => "Ticket Purchase - {$event_summary}",
+ 'Invoice_Date' => date('Y-m-d', strtotime($order->post_date)),
'Status' => 'Paid',
- 'Contact_Name' => $contact_id,
- 'Subject' => "Ticket Purchase - {$event_title}",
- 'Sub_Total' => $order->get_subtotal(),
- 'Tax' => $order->get_total_tax(),
- 'Total' => $order->get_total(),
- 'Balance' => 0,
- 'WordPress_Order_ID' => $order->get_id(),
+ 'Account_Name' => $purchaser_name,
+ 'Description' => "Payment via {$gateway}. Transaction: {$gateway_order_id}",
+ 'Grand_Total' => $total,
+ 'WordPress_Order_ID' => $order->ID,
'Product_Details' => $items
);
}
-}
-?>
\ No newline at end of file
+
+ /**
+ * Prepare attendee data from Event Tickets attendee post
+ *
+ * @param WP_Post $attendee Attendee post object
+ * @return array Attendee data
+ */
+ private function prepare_attendee_data($attendee) {
+ $post_type = $attendee->post_type;
+
+ // Handle different attendee post types
+ if ($post_type === 'tec_tc_attendee') {
+ // Tickets Commerce attendee (meta keys: _tec_tickets_commerce_*)
+ $event_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_event', true);
+ $ticket_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_ticket', true);
+ $order_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_order', true);
+ $full_name = get_post_meta($attendee->ID, '_tec_tickets_commerce_full_name', true);
+ $email = get_post_meta($attendee->ID, '_tec_tickets_commerce_email', true);
+ $checkin = get_post_meta($attendee->ID, '_tec_tickets_commerce_checked_in', true);
+ } else {
+ // PayPal or other attendee types (tribe_tpp_attendees)
+ $event_id = get_post_meta($attendee->ID, '_tribe_tpp_event', true);
+ $ticket_id = get_post_meta($attendee->ID, '_tribe_tpp_product', true);
+ $order_id = get_post_meta($attendee->ID, '_tribe_tpp_order', true);
+ $full_name = get_post_meta($attendee->ID, '_tribe_tickets_full_name', true);
+ $email = get_post_meta($attendee->ID, '_tribe_tickets_email', true);
+ $checkin = get_post_meta($attendee->ID, '_tribe_tpp_checkin', true);
+ }
+
+ // Parse full name into first/last
+ $name_parts = explode(' ', trim($full_name), 2);
+ $first_name = isset($name_parts[0]) ? $name_parts[0] : '';
+ $last_name = isset($name_parts[1]) ? $name_parts[1] : '';
+
+ $event_title = $event_id ? get_the_title($event_id) : '';
+ $ticket_name = $ticket_id ? get_the_title($ticket_id) : '';
+
+ return array(
+ 'attendee_id' => $attendee->ID,
+ 'post_type' => $post_type,
+ 'event_id' => $event_id,
+ 'event_title' => $event_title,
+ 'ticket_id' => $ticket_id,
+ 'ticket_name' => $ticket_name,
+ 'order_id' => $order_id,
+ 'full_name' => $full_name,
+ 'first_name' => $first_name,
+ 'last_name' => $last_name,
+ 'email' => $email,
+ 'checked_in' => !empty($checkin),
+ 'zoho_contact' => array(
+ 'First_Name' => $first_name ?: 'Unknown',
+ 'Last_Name' => $last_name ?: 'Attendee',
+ 'Email' => $email,
+ 'Lead_Source' => 'Event Tickets',
+ 'Contact_Type' => 'Attendee',
+ ),
+ 'zoho_campaign_member' => array(
+ 'Member_Status' => !empty($checkin) ? 'Attended' : 'Registered',
+ )
+ );
+ }
+
+ /**
+ * Prepare RSVP data from Event Tickets RSVP attendee post
+ *
+ * @param WP_Post $rsvp RSVP attendee post object
+ * @return array RSVP data
+ */
+ private function prepare_rsvp_data($rsvp) {
+ $event_id = get_post_meta($rsvp->ID, '_tribe_rsvp_event', true);
+ $ticket_id = get_post_meta($rsvp->ID, '_tribe_rsvp_product', true);
+ $full_name = get_post_meta($rsvp->ID, '_tribe_rsvp_full_name', true);
+ $email = get_post_meta($rsvp->ID, '_tribe_rsvp_email', true);
+ $rsvp_status = get_post_meta($rsvp->ID, '_tribe_rsvp_status', true); // yes, no
+
+ // Parse full name into first/last
+ $name_parts = explode(' ', trim($full_name), 2);
+ $first_name = isset($name_parts[0]) ? $name_parts[0] : '';
+ $last_name = isset($name_parts[1]) ? $name_parts[1] : '';
+
+ $event_title = $event_id ? get_the_title($event_id) : '';
+
+ return array(
+ 'rsvp_id' => $rsvp->ID,
+ 'event_id' => $event_id,
+ 'event_title' => $event_title,
+ 'ticket_id' => $ticket_id,
+ 'full_name' => $full_name,
+ 'first_name' => $first_name,
+ 'last_name' => $last_name,
+ 'email' => $email,
+ 'rsvp_status' => $rsvp_status,
+ 'zoho_lead' => array(
+ 'First_Name' => $first_name ?: 'Unknown',
+ 'Last_Name' => $last_name ?: 'RSVP',
+ 'Email' => $email,
+ 'Lead_Source' => 'Event RSVP',
+ 'Lead_Status' => $rsvp_status === 'yes' ? 'Contacted' : 'Not Interested',
+ ),
+ 'zoho_campaign_member' => array(
+ 'Member_Status' => $rsvp_status === 'yes' ? 'Responded' : 'Not Responded',
+ )
+ );
+ }
+}
\ No newline at end of file