240 lines
11 KiB
Markdown
240 lines
11 KiB
Markdown
# 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 ❌
|
|
```json
|
|
// 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 ⚠️
|
|
```json
|
|
// 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 ✅
|
|
```json
|
|
// 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
|
|
```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
|
|
```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:
|
|
|
|
```json
|
|
{
|
|
"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! 🎉
|