# HubSpot Integration Test Procedure

**Last Updated:** 2026-02-12

Step-by-step procedure for testing the affiliate HubSpot integration and reporting accuracy using Frederik Kiel (`frederik@ordio.com`, Partner ID `AP-28260283-8FF485`).

## Prerequisites

- **HubSpot token:** Valid API token in `v2/config/hubspot-config.php` or environment
- **Partner registry:** Frederik must be in the affiliate partner registry with `AP-28260283-8FF485` (sync only fetches data for registered partners)
- **Repository root:** Run commands from the project root

## Step 1: Run Seed Script

Creates 3 test contacts and 3 test deals in HubSpot:

```bash
php v2/scripts/hubspot/seed-affiliate-test-data.php
```

Options:

- `--partner-id=AP-XXX` – Use a different partner (default: `AP-28260283-8FF485`)
- `--dry-run` – Show what would be created without calling HubSpot API

**Created data:**

| Type     | Count | Purpose                                                            |
| -------- | ----- | ------------------------------------------------------------------- |
| Contacts | 3     | Different lead statuses: Neu, Qualifiziert, Deal/Gewonnen           |
| Deals    | 3     | MRR €89/€150/€200; 2 Active, 1 Paused; all Closed Won              |

The script writes created IDs to `v2/scripts/hubspot/seed-affiliate-test-data-last.json` for cleanup.

## Step 2: Wait for HubSpot Indexing

HubSpot search can take **1–2 minutes** to index new objects. Wait before running the sync.

## Step 3: Run Sync

Trigger the HubSpot sync:

```bash
php v2/cron/sync-affiliate-hubspot.php
```

Or use the **"Sync mit HubSpot"** button in Verwaltung (Admin panel).

## Step 4: Verify Dashboard as frederik@ordio.com

Log in as Frederik and verify each tab:

### Dashboard

- **KPIs:** Conversion funnel, lead count, deal count
- **Conversion Funnel chart:** Leads → Qualified → Deals
- **MRR trend:** Total MRR reflected
- **Lead trend:** Lead count over time

### Leads

- **Table:** 3 leads visible
- **Status filters:** Neu, Qualifiziert, Deal (or Gewonnen)
- **Search / pagination:** Works as expected

### Earnings

- **MRR breakdown:** Total MRR, Active vs Paused
- **MRR donut chart:** Active vs Paused amounts
- **Deal list:** 3 deals with correct MRR (€89, €150, €200)

### Levels

- **Level:** Starter (1–5 deals in 90 days)
- **Progress bar:** Reflects deal count
- **Deals in period:** 3 deals in rolling 90-day window

### Leaderboard

- **Rank:** Frederik’s position shown (if he qualifies)

## Step 5: Run Cleanup

After verification, delete the test data:

```bash
php v2/scripts/hubspot/cleanup-affiliate-test-data.php --from-file=seed-affiliate-test-data-last.json
```

Or with explicit IDs:

```bash
php v2/scripts/hubspot/cleanup-affiliate-test-data.php --partner-id=AP-28260283-8FF485 --contact-ids=ID1,ID2,ID3 --deal-ids=ID4,ID5,ID6
```

The cleanup script does **not** delete the affiliate partner object (Frederik is a real partner).

## Troubleshooting

### Deals not counted (MRR/Level zero)

- **Cause:** HubSpot returns `dealstage` as numeric ID (e.g. `3782778049`), not label.
- **Fix:** The level and MRR calculators use `isDealStageClosedWon()` which resolves numeric stage IDs via pipeline fetch. If still broken, confirm the pipeline stages API returns a Closed Won stage and that the seed script uses the correct stage ID.

### Indexing delay

- **Symptom:** Sync returns 0 contacts or 0 deals.
- **Action:** Wait 2 minutes and run the sync again.

### Contact creation failures (Invalid request)

**Cause:** HubSpot portals can use custom lifecycle stages and lead status values. Ordio uses numeric IDs for some stages (e.g. `999998686` for "Marketing Qualified Lead"); `subscriber` and `QUALIFIED` are not valid.

**Fix:** The seed script fetches valid options at runtime via `GET /crm/v3/properties/0-1/lifecyclestage` and `GET /crm/v3/properties/0-1/hs_lead_status`, then maps Neu/Qualifiziert/Deal to matching values. It uses fallbacks (`lead`, `999998686`, `opportunity`, `NEW`, `OPEN`, `OPEN_DEAL`) if the fetch fails.

**To allow custom stages in HubSpot:** Settings → Objects → Contacts → Lifecycle Stage (and Lead Status). Add or edit options and note the `value` (internal name or numeric ID) for API use.

### Duplicate contacts

- The seed script uses unique emails: `test-frederik-lead-{timestamp}-{n}@example.com`.
- If you run the seed multiple times without cleanup, you may create duplicates. Use the cleanup script before re-seeding.

### Rate limits

- The seed script adds 1–2 second delays between API calls.
- If you see 429 responses, wait and retry.

### Sync not updating data

**Symptom:** Dashboard shows 0 leads / 0 deals / €0 MRR after "Sync mit HubSpot", but HubSpot has contacts and deals for the partner.

**Diagnosis:** Run the diagnostic script (production-safe, read-only):

```bash
php v2/scripts/affiliate/diagnose-sync-frederik.php [--json] [--partner-id=AP-XXX]
```

Use `--partner-id=AP-XXX` to diagnose a specific partner (default: AP-28260283-8FF485).

**Checks:**

1. **Partner in registry:** Script reports `registry.in_registry`. If false, partner is not in `affiliate_partners.json` and sync will skip them. Add via admin or manual JSON edit.
2. **Token/portal:** Script reports `hubspot.contacts_count` and `hubspot.deals_count`. If 0 but HubSpot has data, verify production `HUBSPOT_API_TOKEN` targets the same portal as dev/staging.
3. **Path mismatch:** Script reports `paths.readable_cache_file` vs `paths.writable_cache_file`. If they differ, sync may write to one path while dashboard reads from another. Align writable cache path so both use the same file.
4. **Cron vs web context:** If running via cron, path resolution may differ from web. Ensure data and cache are in a location writable by both web and cron user (or cron runs as same user as web).

## Related Documentation

- [HUBSPOT_INTEGRATION_TESTING_NOTION.md](HUBSPOT_INTEGRATION_TESTING_NOTION.md) – Sales-team overview of integration testing (for Notion import)
- [CRON_SYNC_RUNBOOK.md](CRON_SYNC_RUNBOOK.md) – Sync schedule and manual run
- [ARCHITECTURE.md](ARCHITECTURE.md) – Affiliate system architecture
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) – General affiliate troubleshooting
