# Deployment Checklist - Ordio Loop Affiliate Partner System

**Last Updated:** 2026-04-08

Complete deployment checklist for the Ordio Loop affiliate partner system.

## Pre-Deployment

### Security (pre-launch)

- [ ] **HubSpot API token (production only)**

  - Set `HUBSPOT_API_TOKEN` in the environment. In production (ordio.com or `ORDIO_ENV=production`) the app will not use a fallback and will fail if the token is missing.
  - Do not rely on any fallback token in production.
  - **Verify token is set:** Run `php v2/scripts/affiliate/check-hubspot-token.php` to verify environment variable is accessible
  - **Note:** Read-only affiliate dashboard APIs (`affiliate-dashboard-data.php`, `affiliate-levels.php`) will work with fallback token temporarily, but you should set the environment variable for production

- [ ] **Session and CORS**

  - Session cookie uses HttpOnly, Secure (over HTTPS), SameSite=Lax; strict mode and cookies-only (see `v2/includes/affiliate-auth.php`).
  - Partner login, register, and password-reset APIs use CORS allowlist (no `*`); allowed origins: `https://www.ordio.com`, `https://ordio.com`; localhost allowed in non-production.
  - **Local dev:** Admin page and admin APIs must be served from the **same origin** (same host and port). See [LOCAL_DEV.md](LOCAL_DEV.md).

- [ ] **File access**

  - `.htaccess` blocks direct web access to `v2/data/*.json` and `writable/*.json` (403).
  - Sensitive affiliate JSON files are in `.gitignore` and must not be committed.

- [ ] **Git and production data (do not mix local and production)**

  - **Ignored in `.gitignore`** (never commit; each environment has its own):
    - `v2/data/affiliate_partners.json` – partner registry
    - `v2/data/affiliate_hubspot_cache.json` – HubSpot sync cache
    - `v2/data/affiliate_admin_audit.json` – admin action log
    - `writable/affiliate_partners.json`
    - `writable/affiliate_hubspot_cache.json`
    - `writable/affiliate_admin_audit.json`
    - `writable/affiliate_hubspot_sync.lock` – runtime lock
  - **Deploy:** Push code only; do **not** copy local affiliate JSON/lock files to production.
  - **Git:** `affiliate_partners.json`, `affiliate_hubspot_cache.json`, and `affiliate_hubspot_sync.lock` under `v2/data/` must **not** be tracked (they were removed from the index in 2026-04; if re-added by mistake, `git rm --cached` them). Production deployment rsync excludes these paths so a bad commit cannot overwrite live data.
  - **Production:** Create or migrate partner/cache/audit on the server (see “Initialize JSON files” below). Bootstrap admin: run `set-partner-admin.php` or use `AFFILIATE_ADMIN_EMAILS` in config.

- [ ] **Cron**

  - `v2/cron/sync-affiliate-hubspot.php` runs CLI-only; web requests to it return 403.

- [ ] **Review SECURITY.md**
  - Read [SECURITY.md](SECURITY.md) for full scope and optional improvements (rate limiting, CSRF).

- [ ] **Google OAuth (Partner Login)**
  - Set `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET` in production environment (or deploy `oauth-credentials.php` – see [OAUTH_SOCIAL_LOGIN.md](OAUTH_SOCIAL_LOGIN.md)).
  - Without these, the "Mit Google anmelden" button is hidden on `/partner/login` and `/partner/register`.
  - **Verify:** Run `php v2/scripts/dev-helpers/validate-oauth-setup.php` on production.

- [ ] **Google OAuth (Product Updates Admin — `/produkt-updates-admin`)**
  - Uses the **same** `GOOGLE_OAUTH_CLIENT_ID` / `GOOGLE_OAUTH_CLIENT_SECRET` as partner login, but a **different** callback path. **Authorized redirect URIs** in Google Cloud must include **both** partner and admin callbacks, or Product Updates login returns **Error 400: redirect_uri_mismatch**.
  - Add these **exact** URIs to the same Web OAuth client (adjust if you only use one public host):
    - `https://www.ordio.com/produkt-updates-admin/oauth/callback`
    - `https://ordio.com/produkt-updates-admin/oauth/callback`
  - **Confirm the value your server sends:** On production (SSH), run  
    `php v2/scripts/dev-helpers/show-product-updates-admin-oauth-callback.php`  
    (uses current server env). To simulate www vs apex:  
    `php v2/scripts/dev-helpers/show-product-updates-admin-oauth-callback.php --host=www.ordio.com --https`  
    and repeat with `--host=ordio.com`. Paste the printed line into Google Cloud **unchanged**.
  - **Wrong host behind reverse proxy:** Set `PRODUCT_UPDATES_ADMIN_OAUTH_REDIRECT_URI` to the full public callback URL (must match Console), or set `PRODUCT_UPDATES_ADMIN_OAUTH_USE_FORWARDED_HOST=1` when a **trusted** proxy sets `X-Forwarded-Host`. See [PRODUCT_UPDATES_ADMIN_OAUTH.md](../product-updates/PRODUCT_UPDATES_ADMIN_OAUTH.md).

### HubSpot Setup

- [ ] **Configure API Token**

  - Set `HUBSPOT_API_TOKEN` environment variable (required in production; fallback is development-only and disabled on ordio.com)
  - Verify token format: `pat-{region}-{hash}`
  - **Setup methods:**
    - **Apache/.htaccess:** `SetEnv HUBSPOT_API_TOKEN pat-eu1-your-token-here`
    - **PHP-FPM Pool:** `env[HUBSPOT_API_TOKEN] = pat-eu1-your-token-here`
    - **System Environment:** `export HUBSPOT_API_TOKEN=pat-eu1-your-token-here` (add to `/etc/environment` or server startup script)
    - **cPanel/Shared Hosting:** Use cPanel Environment Variables section
  - **Verify:** Run `php v2/scripts/affiliate/check-hubspot-token.php` to confirm token is set and accessible

- [ ] **Verify API Token and Scopes**

  - Run: `php v2/scripts/hubspot/test-api-token-scopes.php`
  - Verify all required scopes are present
  - Add missing scopes in HubSpot Settings → Integrations → Private Apps if needed

- [ ] **Create Property Group (Manual)**

  - Go to HubSpot Settings → Properties
  - Create property group `affiliate_info` for Contacts
  - Create property group `affiliate_info` for Deals
  - Or run: `php v2/scripts/hubspot/create-affiliate-property-group.php` for instructions

- [ ] **Create Custom Object**

  - Run: `php v2/scripts/hubspot/create-affiliate-custom-object.php`
  - Verify object created: `affiliate_partner`
  - Note object type ID (usually `2-affiliate_partner`)
  - Verify all properties are present

- [ ] **Create Custom Properties**

  - Run: `php v2/scripts/hubspot/setup-affiliate-properties.php`
  - Verify properties created:
    - Contacts: `affiliate_partner_id`, `affiliate_referral_date`
    - Deals: `affiliate_partner_id`, `affiliate_mrr`, `affiliate_subscription_status`
  - Verify properties are in `affiliate_info` group

- [ ] **Verify Complete Setup**

  - Run: `php v2/scripts/hubspot/verify-affiliate-setup.php`
  - Review verification results
  - Fix any issues identified

- [ ] **Run End-to-End Test**

  - Run: `php v2/scripts/hubspot/test-affiliate-integration.php`
  - Verify all tests pass
  - Review test output for any warnings

- [ ] **Automated Setup (Alternative)**
  - Run: `php v2/scripts/hubspot/setup-affiliate-hubspot-complete.php`
  - This runs all setup steps automatically
  - Review setup report for any issues

### Slack Loop Updates (Optional)

- [ ] **Configure Webhook URL**
  - Enable Incoming Webhooks for Ordio Loop app in [Slack API](https://api.slack.com/apps)
  - Add webhook for #loop-updates channel
  - Set `SLACK_LOOP_UPDATES_WEBHOOK_URL` in environment (or use `v2/config/slack-config.local.php`)
  - **Verify:** Run `php v2/scripts/affiliate/test-slack-webhook.php`
  - **Diagnostics:** Run `php v2/scripts/affiliate/diagnose-slack-integration.php --verbose`
  - See [SLACK_LOOP_UPDATES.md](SLACK_LOOP_UPDATES.md) for full setup
  - See [SLACK_TROUBLESHOOTING.md](SLACK_TROUBLESHOOTING.md) for troubleshooting

- [ ] **Slash Commands (Optional)**
  - Create `/loop` Slash Command in Slack API Apps → Ordio Loop
  - **Request URL:** `https://www.ordio.com/v2/api/slack-command-handler.php` (or production URL)
  - Copy **Signing Secret** from Basic Information → App Credentials
  - Set `SLACK_SIGNING_SECRET` in environment (or `slack-config.local.php` → `signing_secret`)
  - Reinstall app to workspace after adding command

- [ ] **Weekly digest cron (optional)**
  - Add cron entry for **Mondays 09:00 Europe/Berlin** (adjust if server cron uses UTC):  
    `0 9 * * 1 cd /path/to/landingpage && php v2/scripts/affiliate/weekly-loop-summary.php`
  - Sends rolling **7-day** metrics to #loop-updates. Legacy path `daily-loop-summary.php` delegates to the same script.

- [ ] **Slack Notification Health Check (Recommended)**
  - Add cron entry for health checks: `0 */6 * * * php /path/to/v2/scripts/affiliate/health-check-slack.php`
  - Checks notification health every 6 hours
  - Sends email alerts to `hady@ordio.com` if failures detected
  - See [SLACK_MONITORING.md](SLACK_MONITORING.md) for monitoring setup

### File System Setup

- [ ] **Create Data Directories**

  - Ensure `v2/data/` is writable
  - Ensure `writable/data/` exists and is writable (if applicable)
  - Verify fallback paths work correctly

- [ ] **Create Cache Directory**

  - Ensure cache directory is writable
  - Set proper permissions (755)

- [ ] **Create Logs Directory**

  - Ensure `v2/logs/` directory exists and is writable
  - Set proper permissions (755)
  - Dedicated log file `v2/logs/affiliate-slack.log` will be created automatically

- [ ] **Test File Operations**
  - Run: `php tests/affiliate/partner-registration-test.php`
  - Verify file path resolution works
  - Test read/write operations

### Configuration

- [ ] **Update Configuration**

  - Review `v2/config/affiliate-config.php`
  - Verify program name and claim
  - Check level definitions
  - Verify referral URL settings
  - Review session/authentication settings

- [ ] **Email Configuration**
  - Verify SMTP settings in `v2/helpers/affiliate-email.php`
  - Set `SMTP_PASSWORD` environment variable (recommended)
  - Test email sending functionality

### Cron Job Setup

- [ ] **Schedule Production Cron Jobs**

  - Use consolidated crontab: `v2/cron/crontab-production.txt` (affiliate sync, health check, performance analysis).
  - Install: `crontab v2/cron/crontab-production.txt` (run from repo root on server).
  - Or add manually: See [CRON_ENTRY.txt](CRON_ENTRY.txt) for the affiliate sync line.
  - Sync uses a lockfile to prevent overlapping runs; see [CRON_SYNC_RUNBOOK.md](CRON_SYNC_RUNBOOK.md).
  - **GitHub Actions** `.github/workflows/hubspot-sync-cron.yml` hits `cron-webhook-sync.php` **twice daily only** (intentional backup — do not increase frequency for hourly sync). **Hourly data freshness requires this server cron line**; if Verwaltung “Letzte Synchronisation” only updates ~2×/day, server cron is missing or broken, not the workflow.
  - Test cron job manually first: `php v2/cron/sync-affiliate-hubspot.php`
  - Verify sync completes successfully
  - Check logs: `/var/log/affiliate-sync.log` (stdout/stderr from cron)

- [ ] **Post-Deploy: Verify Cron Is Installed**

  - Run on production server: `crontab -l | grep sync-affiliate-hubspot`
  - Expected: Line containing `sync-affiliate-hubspot.php`.
  - Or run: `php v2/scripts/affiliate/verify-cron-installed.php` (exits 0 if installed, 1 if not).

- [ ] **Post-Deploy: Confirm hourly sync is server-driven (not only GitHub)**

  - After ~65 minutes, check `affiliate_hubspot_cache.json` `last_sync` or Partner Verwaltung — it should advance **without** waiting for the twice-daily Actions run. If it only moves at ~09:00/21:00 UTC, fix production `crontab` / paths / logs per [CRON_SYNC_RUNBOOK.md](CRON_SYNC_RUNBOOK.md) troubleshooting.

- [ ] **Post-Deploy: Update Production Crontab** (when changing sync schedule)
  - If deployed from a previous daily schedule, update production crontab from `0 2 * * *` to `0 * * * *`.
  - See [CRON_ENTRY.txt](CRON_ENTRY.txt) for copy-paste crontab line.

- [ ] **Monitor Sync Health**
  - Run: `php v2/scripts/affiliate/monitor-sync-health.php`
  - Verify health check passes
  - Health check runs daily via cron (see crontab-production.txt)
  - **Sync failure alerting:** On sync failure (exit 1), an email is sent to `hady@ordio.com`. No alert on `sync_already_running`.

## Deployment Steps

### 1. Code Deployment

- [ ] **Upload Files**

  - Upload all PHP files to server
  - Upload CSS/JS files
  - Upload test scripts
  - Upload documentation

- [ ] **Set Permissions**

  - Set PHP files to 644
  - Set directories to 755
  - Ensure data directories are writable

- [ ] **Verify File Structure**
  - Check all required files exist
  - Verify includes/paths are correct
  - Test file access

### 2. Database/Storage Setup

- [ ] **Initialize JSON Files**

  - Create `affiliate_partners.json` with empty structure
  - Create `affiliate_hubspot_cache.json` with empty structure
  - Set proper permissions

- [ ] **Test Storage**
  - Create test partner registration
  - Verify data saves correctly
  - Test cache file creation

### 3. HubSpot Integration

- [ ] **Run Setup Scripts**

  - Execute custom object creation (if not done in pre-deployment)
  - Execute property setup (if not done in pre-deployment)
  - Verify all properties exist

- [ ] **Verify Integration**

  - Run: `php v2/scripts/hubspot/verify-affiliate-setup.php`
  - Check all components exist and are accessible
  - Review verification report

- [ ] **Test Integration**
  - Register test partner via registration form
  - Verify HubSpot object created
  - Check all properties are set correctly
  - Verify object appears in HubSpot UI
  - Run: `php v2/scripts/hubspot/test-affiliate-integration.php` for comprehensive test

### 4. Email Functionality

- [ ] **Test Email Sending**

  - Register test partner
  - Verify verification email sent
  - Test password reset email
  - Check email content and formatting

- [ ] **Configure SMTP**
  - Set environment variable for SMTP password
  - Test PHPMailer connection
  - Verify fallback to mail() works

### 5. Authentication

- [ ] **Test Registration**

  - Register new partner
  - Verify email verification works
  - Test login functionality
  - Check session management

- [ ] **Test Password Reset**
  - Request password reset
  - Verify reset email sent
  - Test reset link functionality
  - Verify password change works

### 6. Dashboard Functionality

- [ ] **Test Dashboard Access**

  - Login as partner
  - Verify dashboard loads
  - Check KPI cards display
  - Test chart rendering

- [ ] **Test Data Display**
  - Verify leads display correctly
  - Check earnings calculation
  - Test leaderboard functionality
  - Verify referral URL generation

### 7. Sync Process

- [ ] **Run Initial Sync**

  - Execute sync script manually
  - Verify data fetched from HubSpot
  - Check cache file created
  - Verify MRR calculations

- [ ] **Test Hourly Sync**
  - Wait for scheduled sync (or trigger manually)
  - Verify sync completes
  - Check logs for errors
  - Verify data updates

## Post-Deployment

### Bootstrap admin backfill

- [ ] **Set is_admin for config admins**
  - After deploy, ensure each email in `AFFILIATE_ADMIN_EMAILS` (e.g. `hady@ordio.com`) has `is_admin: true` on their partner record so the admin check works even if session email is missing.
  - Run (dry-run first): `php v2/scripts/affiliate/set-partner-admin.php --email=hady@ordio.com --is-admin=1 --dry-run` then without `--dry-run`.
  - Repeat for any other config admin emails. See [ADMIN_DELETE_PARTNERS.md](ADMIN_DELETE_PARTNERS.md#updating-partner-records-in-production-admin-etc).
  - **Verify:** `php v2/scripts/affiliate/verify-admin-check.php --partner-id=AP-YYYYMMDD-XXXXXX` should show `result: ADMIN` and reason (config or record).

### Testing

- [ ] **Run Test Suites**

  - `php tests/affiliate/partner-registration-test.php`
  - `php tests/affiliate/hubspot-sync-test.php`
  - `php tests/affiliate/mrr-calculation-test.php`
  - Verify all tests pass

- [ ] **Integration Testing**
  - Test complete registration flow
  - Test lead tracking end-to-end
  - Test MRR calculation accuracy
  - Verify level updates

### Monitoring

- [ ] **Set Up Logging**

  - Verify logs are being written
  - Sync stdout/stderr: `/var/log/affiliate-sync.log` (when cron redirects output)
  - Health check: `/var/log/affiliate-sync-health.log`
  - PHP error log: `[Affiliate Sync]` prefix for sync messages
  - Set up log rotation (if needed)

- [ ] **Sync Failure Alerting**
  - On sync failure (exit 1), email sent to `hady@ordio.com` with error details.
  - No alert when `sync_already_running` (another instance holds lock).
  - See [CRON_SYNC_RUNBOOK.md](CRON_SYNC_RUNBOOK.md) for remediation.

- [ ] **Monitor Sync Health**
  - Run health check script: `php v2/scripts/affiliate/monitor-sync-health.php`
  - Health check runs daily at 9:00 UTC via cron (crontab-production.txt)
  - Monitor sync duration and data consistency

### Documentation

- [ ] **Partner UTM & attribution**

  - See [PARTNER_UTM_AND_ATTRIBUTION.md](PARTNER_UTM_AND_ATTRIBUTION.md): partner attribution = `affiliate_partner_id` from `affiliate` param; UTMs use same HubSpot properties; recommend source=affiliate/partner, medium=referral.

- [ ] **Level calculation**

  - See [LEVEL_CALCULATION.md](LEVEL_CALCULATION.md): levels use rolling 90-day window (won deals in last 90 days).
  - **Level override:** Run `php v2/scripts/affiliate/test-level-override-logic.php` (9 tests). Manual verification: set override in Admin UI, run sync (level unchanged), revert (level follows calculated), check HubSpot and audit log. See [LEVEL_CALCULATION.md](LEVEL_CALCULATION.md#manual-verification-post-deployment).

- [ ] **Update Documentation**

  - Verify all documentation is current
  - Update any deployment-specific notes
  - Document any custom configurations

- [ ] **Create Runbook**
  - Document common issues
  - Create troubleshooting guide
  - Document rollback procedures

## Verification Checklist

### Functional Verification

- [ ] Partner registration works
- [ ] Email verification works
- [ ] Login/logout works
- [ ] Password reset works
- [ ] Dashboard displays correctly
- [ ] Leads tracking works
- [ ] MRR calculation is accurate
- [ ] Level updates work
- [ ] Level override (Admin): set manual level, sync preserves it, revert restores automatic; audit log shows level_change/level_revert
- [ ] Referral URLs generate correctly
- [ ] Leaderboard displays correctly

### Integration Verification

- [ ] HubSpot custom object created
- [ ] HubSpot properties exist
- [ ] Lead capture tracks affiliate ID
- [ ] Collect lead tracks affiliate ID
- [ ] Hourly sync runs successfully
- [ ] Cache updates correctly

### Performance Verification

- [ ] Dashboard loads quickly (< 2s)
- [ ] API responses are fast (< 500ms)
- [ ] Sync completes in reasonable time
- [ ] No memory issues
- [ ] No timeout errors

### Security Verification

- [ ] Passwords are hashed
- [ ] Sessions expire correctly
- [ ] Login attempts are rate limited
- [ ] Inputs are validated
- [ ] Outputs are sanitized
- [ ] No sensitive data exposed

## Rollback Plan

If issues occur:

1. **Disable Registration**

   - Temporarily disable registration endpoint
   - Show maintenance message

2. **Restore Previous Version**

   - Restore files from backup
   - Restore data files if corrupted

3. **Fix Issues**

   - Identify root cause
   - Apply fixes
   - Re-test thoroughly

4. **Re-deploy**
   - Follow deployment steps again
   - Verify fixes work

## Support Contacts

- **Technical Issues:** hady@ordio.com
- **HubSpot Issues:** Check HubSpot API status
- **Email Issues:** Verify SMTP configuration

## Related Documentation

- **[Architecture](ARCHITECTURE.md)** - System architecture
- **[API Reference](API_REFERENCE.md)** - API endpoints
- **[Partner Guide](PARTNER_GUIDE.md)** - User guide
- **[Dashboard Guide](DASHBOARD_GUIDE.md)** - Dashboard usage
- **[Partner UTM & Attribution](PARTNER_UTM_AND_ATTRIBUTION.md)** - UTM best practices and attribution (same HubSpot properties; attribution via affiliate ID)
