McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Accept Payments with Paystack in Vue
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Accept Payments with Paystack in Vue

To accept Paystack payments in Vue, create a backend API that initializes transactions with your Paystack secret key. Your Vue frontend calls that API, gets an access code, and opens the Paystack inline popup using PaystackPop. After payment, call your backend to verify the transaction before granting value.

In this article

  1. 1. Architecture Overview
  2. 2. Backend Setup
  3. 3. Inline Popup Checkout in Vue
  4. 4. Reusable usePaystack Composable
  5. 5. Redirect Checkout
  6. 6. Production Checklist
  7. 7. Ship Payments Faster

Architecture Overview

Vue is a frontend framework. Like React and Angular, it runs in the browser. Paystack's API requires a secret key for initialization and verification, so you need a backend. The flow:

  1. Vue component collects the customer email and knows the amount.
  2. Vue calls your backend API with the email and amount.
  3. Your backend calls Paystack's Initialize Transaction endpoint and returns the access code or authorization URL.
  4. Vue opens the Paystack inline popup (or redirects to the authorization URL).
  5. After payment, Vue calls your backend to verify the transaction.

This guide uses Vue 3 with the Composition API. The backend examples use Express.js, but any server-side language works. If you use Nuxt, see Accept Payments with Paystack in Nuxt instead, which handles the backend with Nuxt server routes.

Backend Setup

Create a minimal Express server with initialize and verify endpoints:

// server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({ origin: 'http://localhost:5173' })); // Vite default port
app.use(express.json());

const PAYSTACK_SECRET = process.env.PAYSTACK_SECRET_KEY;

app.post('/api/pay', async (req, res) => {
  const { email, amount } = req.body;
  const reference = 'vue_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);

  const response = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + PAYSTACK_SECRET,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email,
      amount: Math.round(amount * 100),
      reference,
    }),
  });

  const data = await response.json();
  if (!data.status) return res.status(400).json({ error: data.message });

  res.json({
    access_code: data.data.access_code,
    authorization_url: data.data.authorization_url,
    reference: data.data.reference,
  });
});

app.get('/api/verify', async (req, res) => {
  const { reference } = req.query;

  const response = await fetch(
    'https://api.paystack.co/transaction/verify/' + encodeURIComponent(reference),
    { headers: { Authorization: 'Bearer ' + PAYSTACK_SECRET } }
  );

  const data = await response.json();

  if (data.status && data.data.status === 'success') {
    return res.json({ success: true, amount: data.data.amount / 100, currency: data.data.currency });
  }

  res.status(400).json({ success: false });
});

app.listen(4000, () => console.log('Backend running on port 4000'));

Start the server: PAYSTACK_SECRET_KEY=sk_test_xxx node server.js

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Inline Popup Checkout in Vue

Create a checkout component that loads Paystack Inline.js and opens the popup:

<!-- src/components/PaystackCheckout.vue -->
<script setup>
import { ref, onMounted } from 'vue';

const email = ref('');
const loading = ref(false);
const amount = 5000; // 5,000 NGN
const API_URL = 'http://localhost:4000';

onMounted(() => {
  const script = document.createElement('script');
  script.src = 'https://js.paystack.co/v2/inline.js';
  script.async = true;
  document.head.appendChild(script);
});

async function handlePay() {
  if (!email.value) return;
  loading.value = true;

  try {
    const res = await fetch(API_URL + '/api/pay', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email: email.value, amount }),
    });

    const data = await res.json();

    if (!data.access_code) {
      alert(data.error || 'Initialization failed');
      loading.value = false;
      return;
    }

    const popup = new window.PaystackPop();
    popup.checkout({
      accessCode: data.access_code,
      onSuccess: async (transaction) => {
        const verifyRes = await fetch(
          API_URL + '/api/verify?reference=' + transaction.reference
        );
        const result = await verifyRes.json();

        if (result.success) {
          alert('Payment confirmed: ' + result.currency + ' ' + result.amount);
        } else {
          alert('Verification failed');
        }
        loading.value = false;
      },
      onCancel: () => {
        loading.value = false;
      },
    });
  } catch (err) {
    alert('Network error');
    loading.value = false;
  }
}
</script>

<template>
  <div class="checkout">
    <h1>Checkout</h1>
    <input v-model="email" type="email" placeholder="Your email" />
    <p>Amount: NGN {{ amount.toLocaleString() }}</p>
    <button @click="handlePay" :disabled="loading || !email">
      {{ loading ? 'Processing...' : 'Pay Now' }}
    </button>
  </div>
</template>

The component loads the Paystack script in onMounted, initializes the transaction through your backend, and opens the popup with the access code. After success, it verifies the payment on your backend before showing a confirmation.

Reusable usePaystack Composable

If you accept payments in multiple places, extract the logic into a composable:

// src/composables/usePaystack.js
import { ref, onMounted } from 'vue';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:4000';

export function usePaystack() {
  const loading = ref(false);
  const scriptLoaded = ref(false);

  onMounted(() => {
    if (document.querySelector('script[src*="paystack"]')) {
      scriptLoaded.value = true;
      return;
    }
    const script = document.createElement('script');
    script.src = 'https://js.paystack.co/v2/inline.js';
    script.onload = () => { scriptLoaded.value = true; };
    document.head.appendChild(script);
  });

  async function pay({ email, amount, onSuccess, onCancel }) {
    loading.value = true;

    const res = await fetch(API_URL + '/api/pay', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, amount }),
    });

    const data = await res.json();

    if (!data.access_code) {
      loading.value = false;
      throw new Error(data.error || 'Failed to initialize');
    }

    const popup = new window.PaystackPop();
    popup.checkout({
      accessCode: data.access_code,
      onSuccess: async (transaction) => {
        const verifyRes = await fetch(
          API_URL + '/api/verify?reference=' + transaction.reference
        );
        const result = await verifyRes.json();
        loading.value = false;

        if (result.success && onSuccess) {
          onSuccess(result);
        }
      },
      onCancel: () => {
        loading.value = false;
        if (onCancel) onCancel();
      },
    });
  }

  return { pay, loading, scriptLoaded };
}

Usage in any component:

<script setup>
import { usePaystack } from '../composables/usePaystack';

const { pay, loading } = usePaystack();

function buyProduct() {
  pay({
    email: 'customer@example.com',
    amount: 2500,
    onSuccess: (result) => {
      console.log('Paid:', result.currency, result.amount);
    },
  });
}
</script>

<template>
  <button @click="buyProduct" :disabled="loading">Buy for NGN 2,500</button>
</template>

Redirect Checkout

For a simpler approach where the customer leaves your Vue app to pay on Paystack's hosted page:

async function handleRedirectPay() {
  const res = await fetch(API_URL + '/api/pay', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: email.value, amount: 5000 }),
  });

  const data = await res.json();

  if (data.authorization_url) {
    window.location.href = data.authorization_url;
  }
}

Set the callback URL in your backend initialization to point to a route in your Vue app (for example, /payment/callback). Use Vue Router to handle that route and verify the payment on mount using the reference from the query string.

Production Checklist

  1. CORS. Restrict your backend CORS to your Vue app's production domain.
  2. HTTPS. Both your Vue app and backend must use HTTPS in production.
  3. Environment variables. Use VITE_API_URL to point to your production backend. Switch to live Paystack keys.
  4. Amount validation. Validate the amount on your backend. Look up the correct price from your database rather than trusting the frontend.
  5. Webhooks. Set up a webhook endpoint on your backend for reliable payment confirmation.
  6. Idempotent fulfillment. Both webhook and callback can fire for the same transaction. Use a database flag to prevent double-granting.

Ship Payments Faster

This guide is part of the Paystack integration guides by language and framework series.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Vue runs in the browser. Your Paystack secret key must live on a separate backend server. Never import it in a .vue file.
  • ✓The Composition API composable pattern works well for encapsulating Paystack payment logic. Create a usePaystack composable for reuse across components.
  • ✓Load Paystack Inline.js dynamically in onMounted. Do not install it as an npm package; load the official CDN script.
  • ✓After the popup onSuccess callback fires, verify the transaction on your backend. The popup callback alone is not proof of payment.
  • ✓Amounts must be in the smallest currency unit (kobo for NGN, pesewas for GHS, cents for ZAR/KES/USD).
  • ✓Use reactive refs for loading states to disable the pay button during processing and prevent double submissions.

Frequently Asked Questions

Do I need the vue-paystack npm package?
No. Community packages exist but are not maintained by Paystack and may lag behind API changes. Loading the official Paystack Inline.js from the CDN gives you the same functionality with fewer dependencies.
Can I use Paystack with Vue 2?
Yes. The Paystack Inline.js script is framework-agnostic. In Vue 2, you would use the Options API instead of the Composition API, but the Paystack integration logic is the same. Load the script, call your backend, and open the popup.
How do I handle the popup on mobile devices?
The Paystack inline popup works on mobile browsers. However, some mobile browsers block popups. If you experience issues, use the redirect checkout approach instead, which works reliably on all devices and browsers.
What if the customer pays but my verification call fails?
This is why webhooks are essential. Even if the verification call from your Vue app fails (network error, server down), the webhook will deliver the payment confirmation to your backend independently. Set up a webhook handler that can fulfill orders on its own.
Can I use Pinia to manage payment state?
You can, but it is usually not necessary. Payment state is short-lived: the customer pays, you verify, and the transaction is done. A composable with reactive refs handles this well. Use Pinia if you need to share payment history or subscription status across multiple components.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Paystack Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Verify Paystack Payments in Svelte and SvelteKit

Verify Paystack transactions in SvelteKit using server endpoints for popup verification and load functions for redirect callback verification. Covers amount validation, idempotent fulfillment, and SSR verification for instant results.

Read more

Handle Paystack Webhooks in Flask

Receive and verify Paystack webhooks in your Flask application. This guide covers accessing request.data for raw bytes, HMAC SHA-512 signature verification, processing charge.success events, and making handlers idempotent.

Read more

Build Paystack Subscriptions in ASP.NET Core

Implement Paystack recurring billing in ASP.NET Core. This guide covers creating plans, subscribing customers through transaction initialization, handling renewal webhooks with C#, and cancelling subscriptions.

Read more

Offline-Tolerant Payment Flows with Paystack

In many African markets, network connectivity is unreliable. Customers start payments on slow 2G connections, lose signal mid-checkout, or switch between WiFi and mobile data. This guide shows you how to build payment flows that work even when the network does not.

Read more

Debugging Paystack with the Transaction Timeline

When a customer says "I paid but nothing happened," you need more than the transaction status. The Transaction Timeline API shows every step that occurred between the customer clicking Pay and your webhook firing (or not firing). It is the closest thing to an X-ray for Paystack payments.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.