I'm trying to have my application using React, trying to retrieve the users PayPal Email address, but everything I do returns an 'Error 400 - Bad Request - Unable to fetch details to serve the request'.
Any idea why? I have double checked my return URI in PayPal, as well as my client / secret ID's.
My backend:
export const handlePaypalOAuthCallback = async (req, res) => { try { const { code, state } = req.query; console.log('PayPal callback received:', { code: code ? 'present' : 'missing', state: state ? 'present' : 'missing', fullQuery: req.query }); if (!code) { throw new Error('Authorization code not received'); } if (!state) { throw new Error('User ID not received in state parameter'); } const CLIENT_ID = "xxx"; const CLIENT_SECRET = "xxx"; if (!CLIENT_ID || !CLIENT_SECRET) { console.error('Missing PayPal credentials'); throw new Error('Server configuration error'); } const auth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'); console.log('Preparing token request:', { url: 'https://api-m.sandbox.paypal.com/v1/oauth2/token', grantType: 'authorization_code', hasCode: !!code, hasAuth: !!auth, redirectUri: '<my return uri>/api/paypal/oauth/callback' }); const params = new URLSearchParams(); params.append('grant_type', 'authorization_code'); params.append('code', code); params.append('redirect_uri', 'http://dev.gotasker.com.au:4000/api/paypal/oauth/callback'); // Add these additional parameters params.append('scope', 'openid email'); params.append('client_id', CLIENT_ID); params.append('client_secret', CLIENT_SECRET); try { // Get access token const tokenResponse = await axios.post('https://api-m.sandbox.paypal.com/v1/oauth2/token', params, { headers: {'Authorization': `Basic ${auth}`,'Content-Type': 'application/x-www-form-urlencoded','PayPal-Request-Id': `${Date.now()}`,'Accept': 'application/json','Accept-Language': 'en_US' } } ); console.log('Token response received:', { hasAccessToken: !!tokenResponse.data.access_token, tokenType: tokenResponse.data.token_type, scope: tokenResponse.data.scope, status: tokenResponse.status, headers: tokenResponse.headers }); const accessToken = tokenResponse.data.access_token; // Log token details for debugging (first few chars only) console.log('Access token details:', { prefix: accessToken.substring(0, 10), length: accessToken.length, tokenType: tokenResponse.data.token_type }); if (!accessToken || typeof accessToken !== 'string') { throw new Error('Invalid access token received from PayPal'); } // Make the userInfo request with proper authorization header const userInfoResponse = await axios.get('https://api-m.sandbox.paypal.com/v1/identity/oauth2/userinfo', // Changed URL { headers: {'Authorization': `Bearer ${accessToken}`,'Content-Type': 'application/json','Accept': 'application/json','PayPal-Request-Id': `${Date.now()}`, }, } ); // Log successful userInfo response console.log('User info response received:', { status: userInfoResponse.status, hasData: !!userInfoResponse.data, dataKeys: Object.keys(userInfoResponse.data) }); const paypalEmail = userInfoResponse.data.email; if (!paypalEmail) { throw new Error('PayPal email not received in user info response'); } // Update user with PayPal email const userId = state; const user = await UserModel.findById(userId); if (!user) { throw new Error('User not found'); } user.paypalEmail = paypalEmail; user.paypalVerified = true; await user.save(); console.log('User PayPal email updated successfully:', { userId: user._id, verified: true }); // Redirect back to frontend with success res.redirect(`${process.env.FRONTEND_URL}/account?paypal=success`); } catch (error) { // Enhanced error logging console.error('API request failed:', { status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, message: error.message, headers: error.response?.headers, config: { url: error.config?.url, method: error.config?.method, headers: { ...error.config?.headers, Authorization: error.config?.headers?.Authorization ? `${error.config.headers.Authorization.substring(0, 10)}...` : 'missing' } }, wwwAuthenticate: error.response?.headers?.['www-authenticate'] }); throw error; } } catch (error) { console.error('PayPal OAuth error:', { message: error.message, response: error.response?.data, status: error.response?.status, statusText: error.response?.statusText }); res.redirect(`${process.env.FRONTEND_URL}/account?paypal=error&message=${encodeURIComponent(error.message)}`); }};
My frontend:
const PayPalLoginCard = () => { const { user, updateUserProfile } = useContext(AuthContext); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); const [successMessage, setSuccessMessage] = useState(''); const location = useLocation(); const navigate = useNavigate(); useEffect(() => { // Handle OAuth response const searchParams = new URLSearchParams(location.search); const paypalStatus = searchParams.get('paypal'); if (paypalStatus === 'success') { setSuccessMessage('PayPal account connected successfully'); // Remove query params navigate('/account', { replace: true }); } else if (paypalStatus === 'error') { setError(searchParams.get('message') || 'Failed to connect PayPal account'); navigate('/account', { replace: true }); } }, [location, navigate]); const handlePayPalLogin = () => { try { const PAYPAL_CLIENT_ID = "xx"; const REDIRECT_URI = 'http://dev.gotasker.com.au:4000/api/paypal/oauth/callback'; const userId = user?.id; if (!userId) { throw new Error('User ID not available'); } // Updated scopes and parameters const scopes = 'openid email'; const authUrl = `https://www.sandbox.paypal.com/signin/authorize?client_id=${PAYPAL_CLIENT_ID}&response_type=code&scope=${scopes}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&state=${userId}&nonce=${Date.now()}`; window.location.href = authUrl; } catch (error) { console.error('Error initiating PayPal login:', error); setError(error.message || 'Failed to initiate PayPal login'); } }; const handleDisconnect = async () => { setIsLoading(true); setError(''); setSuccessMessage(''); try { const response = await axios.post('http://dev.gotasker.com.au:4000/api/paypal/disconnect', {}, { headers: { Authorization: `Bearer ${localStorage.getItem('token')}`, }, } ); if (response.data.success) { await updateUserProfile({ paypalEmail: null }); setSuccessMessage('PayPal account disconnected successfully'); } } catch (error) { setError('Failed to disconnect PayPal account'); } finally { setIsLoading(false); } };
I have verified my Client/Secret ID, I have triple checked my return URI.