Files
2026-10-19 15:22:52 -07:00

11 KiB

PayPal Merchant Onboarding Flow

BEFORE FIX (The Problem) ❌

┌─────────────────────────────────────────────────────────────────┐
│  1. User clicks "Connect with PayPal"                           │
│     → onboard.php creates referral                              │
│     → Saves to pending_onboarding.json                          │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  2. Redirected to PayPal Onboarding                             │
│     → User sees business account signup                         │
│     → User CLOSES browser WITHOUT completing ❌                 │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  3. Returns to return.php                                       │
│     → Queries PayPal API for merchant_id                        │
│     → PayPal returns: "NEG5HMUKCTQNL" (PARTNER ID!) ❌         │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  4. OLD CODE: Accepts partner ID as merchant ID ❌              │
│     → Saves "NEG5HMUKCTQNL" to merchants.json                   │
│     → WRONG! This is YOUR ID, not the merchant's!               │
└─────────────────────────────────────────────────────────────────┘
                              ↓
                        STUCK FOREVER 😞

AFTER FIX (The Solution) ✅

┌─────────────────────────────────────────────────────────────────┐
│  1. User clicks "Connect with PayPal"                           │
│     → onboard.php creates referral                              │
│     → Saves to pending_onboarding.json                          │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  2. Redirected to PayPal Onboarding                             │
│     → User sees business account signup                         │
└─────────────────────────────────────────────────────────────────┘
                         ↙          ↘
                  SCENARIO A       SCENARIO B
         (Incomplete)            (Complete)
                
┌──────────────────────────┐  ┌──────────────────────────┐
│  User CLOSES browser     │  │  User COMPLETES all      │
│  WITHOUT finishing ❌    │  │  steps at PayPal ✅      │
└──────────────────────────┘  └──────────────────────────┘
            ↓                              ↓
┌──────────────────────────┐  ┌──────────────────────────┐
│  Returns to return.php   │  │  Returns to return.php   │
│  PayPal returns:         │  │  PayPal returns:         │
│  "NEG5HMUKCTQNL"        │  │  "ABC123XYZ456" ✅       │
│  (partner ID)            │  │  (real merchant ID)      │
└──────────────────────────┘  └──────────────────────────┘
            ↓                              ↓
┌──────────────────────────┐  ┌──────────────────────────┐
│  NEW CODE:               │  │  NEW CODE:               │
│  ✅ REJECTS partner ID  │  │  ✅ ACCEPTS merchant ID  │
│  ✅ Marks as incomplete  │  │  ✅ Saves to merchants   │
│  ✅ Shows warning        │  │  ✅ Shows success        │
└──────────────────────────┘  └──────────────────────────┘
            ↓                              ↓
┌──────────────────────────┐  ┌──────────────────────────┐
│  Dashboard shows:        │  │  Dashboard shows:        │
│  ⚠️ Incomplete          │  │  ✅ Connected           │
│  "Complete PayPal        │  │  "Ready to accept        │
│   signup to continue"    │  │   payments"              │
└──────────────────────────┘  └──────────────────────────┘

Data Structure Comparison

BEFORE FIX ❌

// pending_onboarding.json
{
    "school_ab32258ab0": {
        "school": "m",
        "created_at": 1760884706,
        "partner_referral_id": "YzU2NGVl...",
        "referral_token": "YzU2NGVl...",
        "merchant_id": "NEG5HMUKCTQNL",  ← WRONG! This is partner ID
        "status": "connected"             ← FALSE! Not really connected
    }
}

AFTER FIX - Incomplete ⚠️

// pending_onboarding.json
{
    "school_ab32258ab0": {
        "school": "m",
        "created_at": 1760884706,
        "partner_referral_id": "YzU2NGVl...",
        "referral_token": "YzU2NGVl...",
        "status": "incomplete_onboarding",  ← Honest status
        "note": "Merchant did not complete PayPal onboarding",
        "last_return_at": 1760885000
    }
}

AFTER FIX - Complete ✅

// pending_onboarding.json
{
    "school_3387003d82": {
        "school": "b",
        "created_at": 1760797314,
        "partner_referral_id": "MTQ2Njlm...",
        "referral_token": "MTQ2Njlm...",
        "merchant_id": "796GQ4VM8SWSQ",  ← CORRECT! Different ID
        "status": "connected",             ← TRUE! Really connected
        "connected_at": 1760797400
    }
}

// merchants.json
{
    "b": {
        "merchant_id": "796GQ4VM8SWSQ",
        "tracking_id": "school_3387003d82",
        "connected_at": 1760797400
    }
}

Key Changes in Code

return.php

// OLD CODE ❌
if (!empty($resp['body']['merchant_id'])) {
    $merchantId = $resp['body']['merchant_id'];  // Accepts ANY ID
}

// NEW CODE ✅
if (!empty($resp['body']['merchant_id'])) {
    $foundId = $resp['body']['merchant_id'];
    // CRITICAL: Don't accept partner's own ID
    if (strcasecmp($foundId, $config['partner_merchant_id']) !== 0) {
        $merchantId = $foundId;  // Only accept if different
    }
}

sync_merchants.php

// OLD CODE ❌
if (!empty($resp['body']['merchant_id'])) {
    $merchantId = $resp['body']['merchant_id'];  // Blindly accepts
}

// NEW CODE ✅
if (!empty($resp['body']['merchant_id'])) {
    $foundId = $resp['body']['merchant_id'];
    // Don't accept partner ID - means onboarding not completed
    if (strcasecmp($foundId, $config['partner_merchant_id']) !== 0) {
        $merchantId = $foundId;
    }
}

Testing Scenarios

Test 1: Incomplete Onboarding ⚠️

  1. Click "Connect with PayPal"
  2. See PayPal signup page
  3. Immediately click "Return to School Lunch App"
  4. Expected Result:
    • ⚠️ "Onboarding Incomplete" warning
    • Status: incomplete_onboarding
    • NO merchant_id saved

Test 2: Complete Onboarding ✅

  1. Click "Connect with PayPal"
  2. See PayPal signup page
  3. Complete ALL steps (business info, email verification, etc.)
  4. Click "Return to Application"
  5. Expected Result:
    • ✅ "Onboarding completed" success message
    • Status: connected
    • Merchant ID saved (NOT NEG5HMUKCTQNL)

Test 3: Sync Pending ⏳

  1. Complete onboarding but merchant_id not immediately available
  2. Click "🔄 Sync Now"
  3. Expected Result:
    • System queries PayPal API
    • Retrieves merchant_id
    • Updates status to connected

Success Indicators ✅

You'll know it's working when:

  1. Different Merchant IDs: Each merchant gets unique ID (not NEG5HMUKCTQNL)
  2. Clear Status: See connected or incomplete_onboarding (not stuck)
  3. Warning Messages: Incomplete onboardings show warnings
  4. Real Payments: Can create orders with actual merchant IDs

Your Real Example 🎯

From your sync_debug.json, school "b" worked correctly:

{
    "ts": 1760884845,
    "track": "school_3387003d82",
    "school": "b",
    "attempt": "by-tracking",
    "status": 200,
    "body": {
        "merchant_id": "796GQ4VM8SWSQ",  ← SUCCESS! ✅
        "tracking_id": "school_3387003d82"
    }
}

This is what ALL your merchants should look like! 🎉