11 KiB
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 ⚠️
- Click "Connect with PayPal"
- See PayPal signup page
- Immediately click "Return to School Lunch App"
- Expected Result:
- ⚠️ "Onboarding Incomplete" warning
- Status:
incomplete_onboarding - NO merchant_id saved
Test 2: Complete Onboarding ✅
- Click "Connect with PayPal"
- See PayPal signup page
- Complete ALL steps (business info, email verification, etc.)
- Click "Return to Application"
- Expected Result:
- ✅ "Onboarding completed" success message
- Status:
connected - Merchant ID saved (NOT
NEG5HMUKCTQNL)
Test 3: Sync Pending ⏳
- Complete onboarding but merchant_id not immediately available
- Click "🔄 Sync Now"
- Expected Result:
- System queries PayPal API
- Retrieves merchant_id
- Updates status to
connected
Success Indicators ✅
You'll know it's working when:
- Different Merchant IDs: Each merchant gets unique ID (not
NEG5HMUKCTQNL) - Clear Status: See
connectedorincomplete_onboarding(not stuck) - Warning Messages: Incomplete onboardings show warnings
- 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! 🎉