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

# 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 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)

// 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

// 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

# 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.