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.
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:
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.
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.
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.
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>
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.
VITE_API_URL to point to your production backend. Switch to live Paystack keys.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