zurri / CALLBACK_GUIDE.md
nexusbert's picture
push to space
4a285d2
# Paystack Payment Callback Guide
## Overview
The platform now has a complete callback system for handling Paystack payment redirects. This includes both backend processing and frontend display.
## Callback Flow
### 1. Backend Callback Endpoint
**GET** `/api/wallet/callback`
This is a **public endpoint** (no authentication required) that Paystack redirects to after payment. It:
- Receives the payment reference from Paystack query parameters
- Verifies the transaction with Paystack API
- Credits the user's wallet if successful
- Redirects to frontend success/failure page
**Query Parameters (from Paystack):**
- `reference` or `trxref` - Payment reference
**Redirects:**
- Success: `${FRONTEND_URL}/payment/success?reference=...&points=...&balance=...`
- Failure: `${FRONTEND_URL}/payment/failed?error=...&reference=...`
### 2. Frontend Callback Pages
#### Success Page (`/payment/success`)
**Query Parameters:**
- `reference` - Payment reference
- `points` - Points added to wallet
- `balance` - New wallet balance
- `amount` - Amount paid in Naira
- `already_processed` - If payment was already processed
#### Failure Page (`/payment/failed`)
**Query Parameters:**
- `error` - Error message
- `reference` - Payment reference
- `status` - Payment status from Paystack
- `message` - Gateway response message
### 3. Static HTML Callback Page
A fallback callback page is available at `/public/payment-callback.html` that:
- Shows a loading spinner while processing
- Displays success or error messages
- Shows payment details (reference, points, balance)
- Provides links to navigate back to wallet or retry
## Configuration
### Environment Variables
```env
# Backend URL (where Paystack redirects to)
BACKEND_URL=http://localhost:3000
# Frontend URL (where users are redirected after processing)
FRONTEND_URL=http://localhost:3000
# Optional: Custom callback URL (overrides default)
PAYSTACK_CALLBACK_URL=http://localhost:3000/api/wallet/callback
```
### Paystack Dashboard Setup
#### Webhook Configuration (Required)
1. Go to Settings β†’ API Keys & Webhooks
2. Add webhook URL: `https://yourdomain.com/api/wallet/webhook/paystack`
3. Select events: `charge.success` (required)
4. Save configuration
See [WEBHOOK_SETUP.md](./WEBHOOK_SETUP.md) for detailed webhook setup instructions.
#### Callback URL (Optional)
The callback URL is automatically set when initializing payments. You can override it with:
- Environment variable: `PAYSTACK_CALLBACK_URL`
- Default: `${BACKEND_URL}/api/wallet/callback`
## Callback Scenarios
### Scenario 1: Successful Payment (First Time)
1. User completes payment on Paystack
2. Paystack redirects to `/api/wallet/callback?reference=wallet_...`
3. Backend verifies payment with Paystack API
4. Backend credits wallet
5. Backend redirects to frontend success page with details
### Scenario 2: Successful Payment (Already Processed)
1. User completes payment on Paystack
2. Paystack redirects to `/api/wallet/callback?reference=wallet_...`
3. Backend checks for existing transaction (idempotency)
4. If already processed, redirects to success page with `already_processed=true`
5. No duplicate credit occurs
### Scenario 3: Failed Payment
1. User attempts payment on Paystack
2. Payment fails or is abandoned
3. Paystack redirects to `/api/wallet/callback?reference=...`
4. Backend verifies and finds status != 'success'
5. Backend redirects to frontend failure page with error details
### Scenario 4: Webhook Processing (Parallel)
1. Paystack sends webhook to `/api/wallet/webhook/paystack`
2. Webhook handler verifies signature and processes payment
3. This happens independently of redirect callback
4. Both webhook and callback are idempotent (no double-crediting)
## Frontend Integration
### Option 1: Use Backend Redirect (Recommended)
```javascript
// When initializing payment
const response = await fetch('/api/wallet/fund', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ amount: 1000 })
});
const data = await response.json();
// Redirect to Paystack
window.location.href = data.authorization_url;
// Backend callback will handle redirect to your success/failure pages
```
### Option 2: Use Inline Widget + Manual Verification
```javascript
// Use Paystack inline widget
const handler = PaystackPop.setup({
key: data.publicKey,
email: userEmail,
amount: data.payment.amount * 100,
ref: data.reference,
callback: function(response) {
// Verify payment manually
verifyPayment(response.reference);
}
});
async function verifyPayment(reference) {
const response = await fetch(`/api/wallet/verify/${reference}`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
const result = await response.json();
if (result.success) {
// Redirect to success page
window.location.href = `/payment/success?reference=${reference}&points=${result.transaction.amount}&balance=${result.wallet.balance}`;
}
}
```
## Testing
### Test Callback Flow
1. Start server: `npm run dev`
2. Initialize payment: `POST /api/wallet/fund`
3. Use test card: `4084084084084081`
4. After payment, Paystack redirects to callback
5. Check redirect URL for success/failure parameters
### Test with cURL
```bash
# Simulate callback (after payment)
curl "http://localhost:3000/api/wallet/callback?reference=wallet_test_123"
```
## Security Considerations
1. **Public Endpoint**: The callback endpoint is public (no auth) because Paystack redirects to it
2. **Verification**: Always verify with Paystack API (never trust redirect parameters)
3. **Idempotency**: Both callback and webhook check for existing transactions
4. **Signature Verification**: Webhook uses HMAC signature verification
5. **Error Handling**: All errors redirect to frontend with safe error messages
## Error Handling
The callback handler catches all errors and redirects to the failure page with:
- Error message (URL encoded)
- Reference (if available)
- Status (if available)
Frontend should display these safely and allow users to retry or contact support.