Skip to main content
GatePass supports multiple African payment gateways including Paystack and Flutterwave for seamless fiat transactions.

Supported Payment Gateways

GatePass integrates with leading African payment processors:

Paystack

Accept payments via cards, bank transfers, and mobile money in Nigeria and Ghana.

Flutterwave

Process payments across 30+ African countries with multiple payment methods.

Configuration

Environment Variables

Set up your payment gateway credentials:
.env
# Paystack Configuration
VITE_PAYSTACK_PUBLIC_KEY=pk_test_your_paystack_public_key
PAYSTACK_SECRET_KEY=sk_test_your_paystack_secret_key

# Flutterwave Configuration
VITE_FLUTTERWAVE_PUBLIC_KEY=FLWPUBK_TEST-your_flutterwave_public_key
FLW_SECRET_KEY=FLWSECK_TEST-your_flutterwave_secret_key
FLW_SECRET_HASH=your_webhook_secret_hash

# Frontend URL for redirects
FRONTEND_URL=http://localhost:5173
Never expose secret keys in your frontend code. Use VITE_ prefix only for public keys.

Payment Flow

The payment process follows these steps:
1

Initialize Order

Create an order with event details and user information. Returns an order ID and payment reference.
2

Process Payment

Redirect user to payment gateway hosted page or initialize inline checkout.
3

Webhook Verification

Payment gateway sends webhook to confirm payment status.
4

Ticket Minting

Upon successful payment, NFT tickets are minted to user’s wallet (if configured).

Initialize Order

Create a new ticket order before processing payment.
const response = await fetch('https://api.gatepass.app/api/orders/initialize', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    eventId: 'clx123abc',
    tierId: 'tier_vip_001',
    quantity: 2,
    paymentMethod: 'FIAT',
    gateway: 'flutterwave',  // or 'paystack'
    customerEmail: '[email protected]',
    customerName: 'John Doe'
  })
});

const data = await response.json();
console.log(data.orderId);      // Order ID for tracking
console.log(data.checkoutUrl);  // Redirect URL (Flutterwave)
console.log(data.reference);    // Payment reference (Paystack)

Order Parameters

ParameterTypeRequiredDescription
eventIdstringYesEvent identifier
tierIdstringYesTicket tier identifier
quantitynumberYesNumber of tickets (minimum 1)
paymentMethodstringYesPayment type: FIAT or CRYPTO
gatewaystringYes*Gateway: paystack, flutterwave, or mpesa
customerEmailstringNoBuyer’s email (defaults to authenticated user)
customerNamestringNoBuyer’s name (defaults to authenticated user)
Platform Fee: GatePass charges a 2.5% platform fee on top of the ticket price.

Paystack Integration

Initialize Inline Payment

Use Paystack’s inline popup for seamless checkout experience.
<script src="https://js.paystack.co/v2/inline.js"></script>

Verify Payment

Paystack webhook will automatically verify payments, but you can also manually verify:
Manual Verification
const verifyPayment = async (reference: string) => {
  const response = await fetch(
    `https://api.paystack.co/transaction/verify/${reference}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`
      }
    }
  );

  const data = await response.json();
  
  if (data.data.status === 'success') {
    console.log('Payment verified');
    // Update order status
  }
};

Flutterwave Integration

Hosted Payment Page

Redirect users to Flutterwave’s hosted checkout page.
async function initiateFlutterwavePayment(orderData) {
  // Step 1: Initialize order on backend
  const response = await fetch('https://api.gatepass.app/api/orders/initialize', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      eventId: orderData.eventId,
      tierId: orderData.tierId,
      quantity: orderData.quantity,
      paymentMethod: 'FIAT',
      gateway: 'flutterwave',
      customerEmail: orderData.email,
      customerName: orderData.name
    })
  });

  const { ok, orderId, checkoutUrl } = await response.json();

  if (ok && checkoutUrl) {
    // Step 2: Redirect to Flutterwave hosted page
    window.location.href = checkoutUrl;
  }
}

Verify Transaction

Verify Flutterwave payment after redirect:
// On payment success page
const urlParams = new URLSearchParams(window.location.search);
const transactionId = urlParams.get('transaction_id');
const txRef = urlParams.get('tx_ref');

if (transactionId && txRef) {
  const response = await fetch(
    `/api/orders/verify-flutterwave?transaction_id=${transactionId}&tx_ref=${txRef}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }
  );

  const { ok, status } = await response.json();
  
  if (ok && status === 'COMPLETED') {
    console.log('Payment verified successfully');
    // Show success message and tickets
  }
}

Webhook Integration

Webhooks automatically update order status when payments are confirmed.
Always verify webhook signatures to prevent fraudulent requests.

Paystack Webhook

// From: ~/workspace/source/src/packages/server/src/routes/webhooks.ts:79-102

router.post('/paystack', async (req, res) => {
  const secret = process.env.PAYSTACK_SECRET_KEY;
  const signature = req.headers['x-paystack-signature'];

  // Verify signature
  const hash = crypto
    .createHmac('sha512', secret)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (hash !== signature) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const body = req.body;
  
  if (body.event === 'charge.success') {
    const order = await prisma.order.findFirst({
      where: { paystackReference: body.data.reference }
    });

    if (order) {
      // Update order status to COMPLETED
      // Mint NFT tickets if wallet connected
      await finalizeOrder(order.id, String(body.data.id));
    }
  }

  res.json({ ok: true });
});

Flutterwave Webhook

// From: ~/workspace/source/src/packages/server/src/routes/webhooks.ts:104-123

router.post('/flutterwave', async (req, res) => {
  const signature = req.headers['verif-hash'];
  const secretHash = process.env.FLW_SECRET_HASH;

  // Verify signature
  if (signature !== secretHash) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event, data } = req.body;

  if (event === 'charge.completed' && data.status === 'successful') {
    const order = await prisma.order.findFirst({
      where: { flutterwaveReference: data.tx_ref }
    });

    if (order) {
      // Update order and mint tickets
      await finalizeOrder(order.id, String(data.id));
    }
  }

  res.json({ ok: true });
});

Webhook Security

Paystack: Creates HMAC SHA512 hash of request body using secret key.
const hash = crypto
  .createHmac('sha512', PAYSTACK_SECRET_KEY)
  .update(JSON.stringify(req.body))
  .digest('hex');

const isValid = hash === req.headers['x-paystack-signature'];
Flutterwave: Compares secret hash from dashboard with header value.
const isValid = req.headers['verif-hash'] === FLW_SECRET_HASH;

Order Finalization

When payment is successful, the system automatically:
1

Update Order Status

Mark order as COMPLETED with payment transaction ID.
2

Mint NFT Tickets

If user has a connected wallet, mint NFT tickets to their address.
3

Send Notification

Create in-app notification about successful purchase.
4

Generate Tickets

Create ticket records with blockchain transaction hash.
// From: ~/workspace/source/src/packages/server/src/routes/webhooks.ts:13-77

async function finalizeOrder(orderId: string, txId: string) {
  const order = await prisma.order.findUnique({
    where: { id: orderId },
    include: { event: true }
  });

  if (!order || order.paymentStatus === 'COMPLETED') return;

  // Update payment status
  await prisma.order.update({
    where: { id: order.id },
    data: { 
      paymentStatus: 'COMPLETED',
      paymentTxId: txId,
      updatedAt: new Date()
    }
  });

  // Mint NFT tickets if wallet connected
  const user = await prisma.user.findUnique({ where: { id: order.userId } });
  const walletAddress = user?.walletAddress;

  if (walletAddress && order.event.contractAddress) {
    try {
      const { txHash, tokenIds } = await mintTicketsFor(
        order.event.contractAddress,
        abi,
        walletAddress,
        order.quantity
      );

      // Save blockchain transaction
      await prisma.order.update({
        where: { id: order.id },
        data: { blockchainTxHash: txHash }
      });

      // Create ticket records
      for (const tokenId of tokenIds) {
        await prisma.ticket.create({
          data: {
            tokenId,
            contractAddress: order.event.contractAddress,
            chainId: order.event.chainId,
            txHash,
            eventId: order.eventId,
            orderId: order.id
          }
        });
      }
    } catch (err) {
      console.error('Blockchain minting failed:', err);
    }
  }

  // Send notification
  await prisma.notification.create({
    data: {
      userId: order.userId,
      title: 'Ticket Purchase Successful',
      message: `You successfully purchased ${order.quantity} ticket(s).`,
      type: 'SUCCESS'
    }
  });
}

Testing

Test Credentials

Public Key: pk_test_xxx
Secret Key: sk_test_xxx

# Test Cards
Card Number: 4084084084084081
CVV: 408
Expiry: 12/28
PIN: 0000
OTP: 123456

Error Handling

Common payment errors:
ErrorDescriptionSolution
Invalid signatureWebhook signature mismatchVerify secret key configuration
Transaction not foundPayment reference invalidCheck order initialization
Insufficient fundsCustomer has insufficient balanceAsk customer to fund account
Network timeoutPayment gateway unreachableRetry with exponential backoff
For complete payment integration code, refer to:
  • ~/workspace/source/src/packages/server/src/routes/orders.ts
  • ~/workspace/source/src/packages/server/src/routes/webhooks.ts
  • ~/workspace/source/src/packages/server/src/utils/flutterwave.ts

Build docs developers (and LLMs) love